xm-netcdf-loader 1.0.0

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 (130) hide show
  1. package/dist/component/colorLegend.d.ts +25 -0
  2. package/dist/component/loadFile.d.ts +43 -0
  3. package/dist/component/loadonMap.d.ts +46 -0
  4. package/dist/component/statusInfo.d.ts +41 -0
  5. package/dist/composables/useGridLabels.d.ts +13 -0
  6. package/dist/composables/useLeafletMap.d.ts +56 -0
  7. package/dist/composables/useMapRendering.d.ts +38 -0
  8. package/dist/composables/useNetCdf.d.ts +49 -0
  9. package/dist/index.d.ts +9 -0
  10. package/dist/netcdf4-wasm/CONTRIBUTING.md +160 -0
  11. package/dist/netcdf4-wasm/LICENSE +22 -0
  12. package/dist/netcdf4-wasm/README.md +81 -0
  13. package/dist/netcdf4-wasm/dist/constants.d.ts +158 -0
  14. package/dist/netcdf4-wasm/dist/constants.d.ts.map +1 -0
  15. package/dist/netcdf4-wasm/dist/constants.js +249 -0
  16. package/dist/netcdf4-wasm/dist/constants.js.map +1 -0
  17. package/dist/netcdf4-wasm/dist/dimension.d.ts +9 -0
  18. package/dist/netcdf4-wasm/dist/dimension.d.ts.map +1 -0
  19. package/dist/netcdf4-wasm/dist/dimension.js +19 -0
  20. package/dist/netcdf4-wasm/dist/dimension.js.map +1 -0
  21. package/dist/netcdf4-wasm/dist/group.d.ts +35 -0
  22. package/dist/netcdf4-wasm/dist/group.d.ts.map +1 -0
  23. package/dist/netcdf4-wasm/dist/group.js +189 -0
  24. package/dist/netcdf4-wasm/dist/group.js.map +1 -0
  25. package/dist/netcdf4-wasm/dist/index.d.ts +17 -0
  26. package/dist/netcdf4-wasm/dist/index.d.ts.map +1 -0
  27. package/dist/netcdf4-wasm/dist/index.js +49 -0
  28. package/dist/netcdf4-wasm/dist/index.js.map +1 -0
  29. package/dist/netcdf4-wasm/dist/netcdf-getters.d.ts +120 -0
  30. package/dist/netcdf4-wasm/dist/netcdf-getters.d.ts.map +1 -0
  31. package/dist/netcdf4-wasm/dist/netcdf-getters.js +816 -0
  32. package/dist/netcdf4-wasm/dist/netcdf-getters.js.map +1 -0
  33. package/dist/netcdf4-wasm/dist/netcdf-worker.d.ts +2 -0
  34. package/dist/netcdf4-wasm/dist/netcdf-worker.d.ts.map +1 -0
  35. package/dist/netcdf4-wasm/dist/netcdf-worker.js +154 -0
  36. package/dist/netcdf4-wasm/dist/netcdf-worker.js.map +1 -0
  37. package/dist/netcdf4-wasm/dist/netcdf4-wasm.js +2 -0
  38. package/dist/netcdf4-wasm/dist/netcdf4-wasm.wasm +0 -0
  39. package/dist/netcdf4-wasm/dist/netcdf4.d.ts +218 -0
  40. package/dist/netcdf4-wasm/dist/netcdf4.d.ts.map +1 -0
  41. package/dist/netcdf4-wasm/dist/netcdf4.js +1049 -0
  42. package/dist/netcdf4-wasm/dist/netcdf4.js.map +1 -0
  43. package/dist/netcdf4-wasm/dist/slice.d.ts +57 -0
  44. package/dist/netcdf4-wasm/dist/slice.d.ts.map +1 -0
  45. package/dist/netcdf4-wasm/dist/slice.js +60 -0
  46. package/dist/netcdf4-wasm/dist/slice.js.map +1 -0
  47. package/dist/netcdf4-wasm/dist/test-setup.d.ts +13 -0
  48. package/dist/netcdf4-wasm/dist/test-setup.d.ts.map +1 -0
  49. package/dist/netcdf4-wasm/dist/test-setup.js +78 -0
  50. package/dist/netcdf4-wasm/dist/test-setup.js.map +1 -0
  51. package/dist/netcdf4-wasm/dist/types.d.ts +444 -0
  52. package/dist/netcdf4-wasm/dist/types.d.ts.map +1 -0
  53. package/dist/netcdf4-wasm/dist/types.js +3 -0
  54. package/dist/netcdf4-wasm/dist/types.js.map +1 -0
  55. package/dist/netcdf4-wasm/dist/variable.d.ts +36 -0
  56. package/dist/netcdf4-wasm/dist/variable.d.ts.map +1 -0
  57. package/dist/netcdf4-wasm/dist/variable.js +152 -0
  58. package/dist/netcdf4-wasm/dist/variable.js.map +1 -0
  59. package/dist/netcdf4-wasm/dist/wasm-module.d.ts +6 -0
  60. package/dist/netcdf4-wasm/dist/wasm-module.d.ts.map +1 -0
  61. package/dist/netcdf4-wasm/dist/wasm-module.js +1502 -0
  62. package/dist/netcdf4-wasm/dist/wasm-module.js.map +1 -0
  63. package/dist/netcdf4-wasm/package.json +78 -0
  64. package/dist/netcdf4-wasm.wasm +0 -0
  65. package/dist/types/colorsJson.d.ts +36 -0
  66. package/dist/types/netcdf.d.ts +70 -0
  67. package/dist/utils/color.d.ts +277 -0
  68. package/dist/utils/colorScales.d.ts +28 -0
  69. package/dist/utils/colorsJsonService.d.ts +24 -0
  70. package/dist/utils/dataProcessing.d.ts +64 -0
  71. package/dist/utils/errorHandling.d.ts +69 -0
  72. package/dist/utils/imageUtils.d.ts +29 -0
  73. package/dist/utils/performance.d.ts +75 -0
  74. package/dist/wasm/constants.d.ts +158 -0
  75. package/dist/wasm/constants.d.ts.map +1 -0
  76. package/dist/wasm/constants.js +249 -0
  77. package/dist/wasm/constants.js.map +1 -0
  78. package/dist/wasm/dimension.d.ts +9 -0
  79. package/dist/wasm/dimension.d.ts.map +1 -0
  80. package/dist/wasm/dimension.js +19 -0
  81. package/dist/wasm/dimension.js.map +1 -0
  82. package/dist/wasm/group.d.ts +35 -0
  83. package/dist/wasm/group.d.ts.map +1 -0
  84. package/dist/wasm/group.js +189 -0
  85. package/dist/wasm/group.js.map +1 -0
  86. package/dist/wasm/index.d.ts +17 -0
  87. package/dist/wasm/index.d.ts.map +1 -0
  88. package/dist/wasm/index.js +49 -0
  89. package/dist/wasm/index.js.map +1 -0
  90. package/dist/wasm/netcdf-getters.d.ts +120 -0
  91. package/dist/wasm/netcdf-getters.d.ts.map +1 -0
  92. package/dist/wasm/netcdf-getters.js +816 -0
  93. package/dist/wasm/netcdf-getters.js.map +1 -0
  94. package/dist/wasm/netcdf-worker.d.ts +2 -0
  95. package/dist/wasm/netcdf-worker.d.ts.map +1 -0
  96. package/dist/wasm/netcdf-worker.js +154 -0
  97. package/dist/wasm/netcdf-worker.js.map +1 -0
  98. package/dist/wasm/netcdf4-wasm.js +2 -0
  99. package/dist/wasm/netcdf4-wasm.wasm +0 -0
  100. package/dist/wasm/netcdf4.d.ts +218 -0
  101. package/dist/wasm/netcdf4.d.ts.map +1 -0
  102. package/dist/wasm/netcdf4.js +1049 -0
  103. package/dist/wasm/netcdf4.js.map +1 -0
  104. package/dist/wasm/slice.d.ts +57 -0
  105. package/dist/wasm/slice.d.ts.map +1 -0
  106. package/dist/wasm/slice.js +60 -0
  107. package/dist/wasm/slice.js.map +1 -0
  108. package/dist/wasm/test-setup.d.ts +13 -0
  109. package/dist/wasm/test-setup.d.ts.map +1 -0
  110. package/dist/wasm/test-setup.js +78 -0
  111. package/dist/wasm/test-setup.js.map +1 -0
  112. package/dist/wasm/types.d.ts +444 -0
  113. package/dist/wasm/types.d.ts.map +1 -0
  114. package/dist/wasm/types.js +3 -0
  115. package/dist/wasm/types.js.map +1 -0
  116. package/dist/wasm/variable.d.ts +36 -0
  117. package/dist/wasm/variable.d.ts.map +1 -0
  118. package/dist/wasm/variable.js +152 -0
  119. package/dist/wasm/variable.js.map +1 -0
  120. package/dist/wasm/wasm-module.d.ts +6 -0
  121. package/dist/wasm/wasm-module.d.ts.map +1 -0
  122. package/dist/wasm/wasm-module.js +1502 -0
  123. package/dist/wasm/wasm-module.js.map +1 -0
  124. package/dist/xm-netcdf-loader.cjs.js +2 -0
  125. package/dist/xm-netcdf-loader.cjs.js.map +1 -0
  126. package/dist/xm-netcdf-loader.es.js +18532 -0
  127. package/dist/xm-netcdf-loader.es.js.map +1 -0
  128. package/dist/xm-netcdf-loader.umd.js +2 -0
  129. package/dist/xm-netcdf-loader.umd.js.map +1 -0
  130. package/package.json +45 -0
@@ -0,0 +1,1049 @@
1
+ // Main NetCDF4 class implementation
2
+ import { Group } from './group.js';
3
+ import { WasmModuleLoader } from './wasm-module.js';
4
+ import { NC_CONSTANTS } from './constants.js';
5
+ import * as NCGet from './netcdf-getters.js';
6
+ export class NetCDF4 extends Group {
7
+ filename;
8
+ mode;
9
+ options;
10
+ module = null;
11
+ initialized = false;
12
+ ncid = -1;
13
+ _isOpen = false;
14
+ memorySource;
15
+ workerSource;
16
+ worker;
17
+ workerReady;
18
+ constructor(filename, mode = 'r', options = {}) {
19
+ super(null, '', -1);
20
+ this.filename = filename;
21
+ this.mode = mode;
22
+ this.options = options;
23
+ // Set up self-reference for Group methods
24
+ this.netcdf = this;
25
+ }
26
+ async initialize() {
27
+ if (this.initialized)
28
+ return;
29
+ try {
30
+ if (this.workerSource) {
31
+ // This now handles the WORKERFS mounting
32
+ await this.setupWorker();
33
+ this.initialized = true;
34
+ }
35
+ else {
36
+ this.module = await WasmModuleLoader.loadModule(this.options);
37
+ if (this.memorySource) {
38
+ await this.mountMemoryData();
39
+ }
40
+ }
41
+ this.initialized = true;
42
+ // Automatically open the file if a filename was provided
43
+ if (this.filename && !this.workerSource) {
44
+ await this.open();
45
+ }
46
+ }
47
+ catch (error) {
48
+ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
49
+ this.module = this.createMockModule();
50
+ this.initialized = true;
51
+ if (this.filename)
52
+ await this.open();
53
+ }
54
+ else {
55
+ throw error;
56
+ }
57
+ }
58
+ }
59
+ // Python-like factory method
60
+ static async Dataset(filename, mode = 'r', options = {}) {
61
+ const dataset = new NetCDF4(filename, mode, options);
62
+ await dataset.initialize();
63
+ return dataset;
64
+ }
65
+ // Create dataset from Blob
66
+ static async fromBlob(blob, mode = 'r', options = {}) {
67
+ const arrayBuffer = await blob.arrayBuffer();
68
+ return NetCDF4.fromArrayBuffer(arrayBuffer, mode, options);
69
+ }
70
+ // Create dataset from ArrayBuffer
71
+ static async fromArrayBuffer(buffer, mode = 'r', options = {}) {
72
+ const data = new Uint8Array(buffer);
73
+ return NetCDF4.fromMemory(data, mode, options);
74
+ }
75
+ // Create dataset from memory data (Uint8Array or ArrayBuffer)
76
+ static async fromMemory(data, mode = 'r', options = {}, filename) {
77
+ if (!data) {
78
+ throw new Error('Data cannot be null or undefined');
79
+ }
80
+ if (!(data instanceof ArrayBuffer) && !(data instanceof Uint8Array)) {
81
+ throw new Error('Data must be ArrayBuffer or Uint8Array');
82
+ }
83
+ const uint8Data = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
84
+ const virtualFilename = filename || `/tmp/netcdf_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.nc`;
85
+ const dataset = new NetCDF4(virtualFilename, mode, options);
86
+ dataset.memorySource = {
87
+ data: uint8Data,
88
+ filename: virtualFilename
89
+ };
90
+ await dataset.initialize();
91
+ return dataset;
92
+ }
93
+ // New factory for Blob/File (local, no full preload)
94
+ static async fromBlobLazy(blob, options = {}) {
95
+ // IMPORTANT: Keep this path consistent with the mount logic in the worker
96
+ const mountPoint = '/working';
97
+ const baseName = `netcdf_lazy_${Date.now()}.nc`;
98
+ const fullPath = `${mountPoint}/${baseName}`;
99
+ const dataset = new NetCDF4(fullPath, 'r', options);
100
+ // Store the raw blob. The worker will mount it via WORKERFS
101
+ dataset.workerSource = { blob, filename: fullPath };
102
+ await dataset.initialize();
103
+ // After worker is set up, open the file
104
+ await dataset.open();
105
+ return dataset;
106
+ }
107
+ async open() {
108
+ if (this._isOpen)
109
+ return;
110
+ if (!this.filename || this.filename.trim() === '') {
111
+ throw new Error('No filename specified');
112
+ }
113
+ // Check for valid modes early, before any WASM operations
114
+ const validModes = ['r', 'w', 'w-', 'a', 'r+'];
115
+ if (!validModes.includes(this.mode)) {
116
+ throw new Error(`Unsupported mode: ${this.mode}`);
117
+ }
118
+ // Worker path
119
+ if (this.worker) {
120
+ // Wait for worker to be ready first
121
+ await this.workerReady;
122
+ const modeValue = this.mode === 'r' ? NC_CONSTANTS.NC_NOWRITE : NC_CONSTANTS.NC_WRITE;
123
+ this.ncid = await this.callWorker('open', { path: this.filename, modeValue });
124
+ this.groupId = this.ncid;
125
+ this._isOpen = true;
126
+ return;
127
+ }
128
+ if (this.mode === 'w' || this.mode === 'w-') {
129
+ // Create new file
130
+ let createMode = NC_CONSTANTS.NC_CLOBBER;
131
+ if (this.options.format === 'NETCDF4') {
132
+ createMode |= NC_CONSTANTS.NC_NETCDF4;
133
+ }
134
+ const result = await this.createFile(this.filename, createMode);
135
+ this.ncid = result;
136
+ this.groupId = result;
137
+ }
138
+ else if (this.mode === 'r' || this.mode === 'a' || this.mode === 'r+') {
139
+ // Open existing file
140
+ const modeValue = this.mode === 'r' ? NC_CONSTANTS.NC_NOWRITE : NC_CONSTANTS.NC_WRITE;
141
+ this.ncid = await this.openFile(this.filename, this.mode);
142
+ this.groupId = this.ncid;
143
+ // Load existing data from mock storage if in test mode
144
+ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
145
+ this.loadMockDimensions();
146
+ }
147
+ else {
148
+ await this.load();
149
+ }
150
+ }
151
+ this._isOpen = true;
152
+ }
153
+ // Property access similar to Python API
154
+ get file_format() {
155
+ return this.options.format || 'NETCDF4';
156
+ }
157
+ get disk_format() {
158
+ return this.file_format;
159
+ }
160
+ get filepath() {
161
+ return this.filename || '';
162
+ }
163
+ get isopen() {
164
+ return this._isOpen;
165
+ }
166
+ // Check if module is initialized
167
+ isInitialized() {
168
+ return this.initialized;
169
+ }
170
+ getModule() {
171
+ if (!this.module) {
172
+ throw new Error('NetCDF4 module not initialized. Call initialize() first.');
173
+ }
174
+ return this.module;
175
+ }
176
+ // Close method
177
+ async close() {
178
+ if (this._isOpen && this.ncid >= 0) {
179
+ await this.closeFile(this.ncid);
180
+ this._isOpen = false;
181
+ this.ncid = -1;
182
+ }
183
+ }
184
+ // Sync method (flush to disk)
185
+ async sync() {
186
+ if (this._isOpen) {
187
+ // TODO: Implement nc_sync when available
188
+ console.warn('sync() not yet implemented');
189
+ }
190
+ }
191
+ // Context manager support (Python-like)
192
+ async __aenter__() {
193
+ if (!this.initialized) {
194
+ await this.initialize();
195
+ }
196
+ return this;
197
+ }
198
+ async __aexit__() {
199
+ await this.close();
200
+ }
201
+ // Low-level NetCDF operations (used by Group methods)
202
+ async openFile(path, mode = 'r') {
203
+ const module = this.getModule();
204
+ const modeValue = mode === 'r' ? NC_CONSTANTS.NC_NOWRITE :
205
+ mode === 'w' ? NC_CONSTANTS.NC_WRITE :
206
+ NC_CONSTANTS.NC_WRITE;
207
+ const result = module.nc_open(path, modeValue);
208
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
209
+ throw new Error(`Failed to open NetCDF file: ${path} (error: ${result.result})`);
210
+ }
211
+ return result.ncid;
212
+ }
213
+ async createFile(path, mode = NC_CONSTANTS.NC_CLOBBER) {
214
+ const module = this.getModule();
215
+ const result = module.nc_create(path, mode);
216
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
217
+ throw new Error(`Failed to create NetCDF file: ${path} (error: ${result.result})`);
218
+ }
219
+ return result.ncid;
220
+ }
221
+ async closeFile(ncid) {
222
+ if (this.worker) {
223
+ this.callWorker('close');
224
+ }
225
+ else {
226
+ const module = this.module;
227
+ if (!module)
228
+ throw new Error("Failed to load module. Ensure module is initialized before calling methods");
229
+ const result = module.nc_close(ncid);
230
+ if (result !== NC_CONSTANTS.NC_NOERR) {
231
+ throw new Error(`Failed to close NetCDF file with ID: ${ncid} (error: ${result})`);
232
+ }
233
+ }
234
+ }
235
+ requestId = 0;
236
+ async callWorker(type, payload = {}) {
237
+ if (!this.worker)
238
+ throw new Error("Worker not initialized");
239
+ const id = ++this.requestId;
240
+ return new Promise((resolve, reject) => {
241
+ const handler = (e) => {
242
+ // Only handle messages that match our request ID
243
+ if (e.data.id !== id)
244
+ return;
245
+ if (e.data.success) {
246
+ resolve(e.data.result);
247
+ }
248
+ else {
249
+ reject(new Error(e.data.error || `Worker error in ${type}`));
250
+ }
251
+ this.worker.removeEventListener('message', handler);
252
+ };
253
+ this.worker.addEventListener('message', handler);
254
+ this.worker.postMessage({
255
+ id, // Include the ID in the request
256
+ type,
257
+ ncid: this.ncid,
258
+ ...payload
259
+ });
260
+ });
261
+ }
262
+ async getGlobalAttributes(groupPath) {
263
+ if (this.worker) {
264
+ return this.callWorker('getGlobalAttributes', { groupPath });
265
+ }
266
+ else {
267
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
268
+ return NCGet.getGlobalAttributes(this.module, this.ncid, groupPath);
269
+ }
270
+ }
271
+ async getFullMetadata(groupPath) {
272
+ if (this.worker) {
273
+ return this.callWorker('getFullMetadata', { groupPath });
274
+ }
275
+ else {
276
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
277
+ return NCGet.getFullMetadata(this.module, this.ncid, groupPath);
278
+ }
279
+ }
280
+ async getAttributeValues(varid, attname) {
281
+ if (this.worker) {
282
+ return this.callWorker('getAttributeValues', { varid, attname });
283
+ }
284
+ else {
285
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
286
+ return NCGet.getAttributeValues(this.module, this.ncid, varid, attname);
287
+ }
288
+ }
289
+ async getDimCount(ncid = this.ncid) {
290
+ if (this.worker) {
291
+ return this.callWorker('getDimCount', { ncid });
292
+ }
293
+ else {
294
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
295
+ return NCGet.getDimCount(this.module, ncid);
296
+ }
297
+ }
298
+ async getGroupVariables(groupPath) {
299
+ if (this.worker) {
300
+ return this.callWorker('getGroupVariables', { groupPath });
301
+ }
302
+ else {
303
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
304
+ return NCGet.getGroupVariables(this.module, this.ncid, groupPath);
305
+ }
306
+ }
307
+ async getVarIDs(ncid = this.ncid) {
308
+ if (this.worker) {
309
+ return this.callWorker('getVarIDs', { ncid });
310
+ }
311
+ else {
312
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
313
+ return NCGet.getVarIDs(this.module, ncid);
314
+ }
315
+ }
316
+ async getDimIDs(ncid = this.ncid) {
317
+ if (this.worker) {
318
+ return this.callWorker('getDimIDs', { ncid });
319
+ }
320
+ else {
321
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
322
+ return NCGet.getDimIDs(this.module, ncid);
323
+ }
324
+ }
325
+ async getDim(dimid, ncid = this.ncid) {
326
+ if (this.worker) {
327
+ return this.callWorker('getDim', { dimid, ncid });
328
+ }
329
+ else {
330
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
331
+ return NCGet.getDim(this.module, ncid, dimid);
332
+ }
333
+ }
334
+ async getDims(groupPath) {
335
+ if (this.worker) {
336
+ return this.callWorker('getDims', { groupPath });
337
+ }
338
+ else {
339
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
340
+ return NCGet.getDims(this.module, this.ncid, groupPath);
341
+ }
342
+ }
343
+ async getVarCount(ncid = this.ncid) {
344
+ if (this.worker) {
345
+ return this.callWorker('getVarCount', { ncid });
346
+ }
347
+ else {
348
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
349
+ return NCGet.getVarCount(this.module, ncid);
350
+ }
351
+ }
352
+ async getAttributeName(varid, attId) {
353
+ if (this.worker) {
354
+ return this.callWorker('getAttributeName', { varid, attId });
355
+ }
356
+ else {
357
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
358
+ return NCGet.getAttributeName(this.module, this.ncid, varid, attId);
359
+ }
360
+ }
361
+ async getVariableInfo(variable, groupPath) {
362
+ if (this.worker) {
363
+ return this.callWorker('getVariableInfo', { variable, groupPath });
364
+ }
365
+ else {
366
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
367
+ return NCGet.getVariableInfo(this.module, this.ncid, variable, groupPath);
368
+ }
369
+ }
370
+ async getVariableArray(variable, groupPath) {
371
+ if (this.worker) {
372
+ return this.callWorker('getVariableArray', { variable, groupPath });
373
+ }
374
+ else {
375
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
376
+ return NCGet.getVariableArray(this.module, this.ncid, variable, groupPath);
377
+ }
378
+ }
379
+ async getSlicedVariableArray(variable, start, count, groupPath) {
380
+ if (this.worker) {
381
+ return this.callWorker('getSlicedVariableArray', { variable, start, count, groupPath });
382
+ }
383
+ else {
384
+ // Main thread path is already synchronous (or could be wrapped in Promise.resolve)
385
+ return NCGet.getSlicedVariableArray(this.module, this.ncid, variable, start, count, groupPath);
386
+ }
387
+ }
388
+ /**
389
+ * slicing and indexing convenience method.
390
+ *
391
+ * Each element of `selection` corresponds to one dimension of the variable:
392
+ * - 'all' or `null` → all elements of that dimension
393
+ * - `number` → scalar index (dimension is collapsed)
394
+ * - `slice(stop)` → elements [0, stop)
395
+ * - `slice(start, stop)` → elements [start, stop)
396
+ * - `slice(start, stop, step)` → strided subset; steps are always positive, reading is always forward, you can achieve negative step by reversing the dimension after reading
397
+ *
398
+ * Strided reads use nc_get_vars_* under the hood.
399
+ * Returns a flat typed array; caller infers shape from non-collapsed dims.
400
+ *
401
+ * @example
402
+ * // Variable shape [time, lat, lon]
403
+ * // Read 10 time steps, all lats, first lon:
404
+ * const data = await dataset.get("temperature", [slice(0, 10), 'all', 0]);
405
+ *
406
+ * @example
407
+ * // Every other element along time:
408
+ * const data = await dataset.get("temperature", [slice(0, 100, 2), null, null]);
409
+ *
410
+ */
411
+ async get(variable, selection, groupPath, options) {
412
+ if (this.worker) {
413
+ return this.callWorker('getVariableArrayWithSelection', {
414
+ ncid: this.ncid, variable, selection, groupPath, options
415
+ });
416
+ }
417
+ else {
418
+ return NCGet.getVariableArrayWithSelection(this.module, this.ncid, variable, selection, groupPath, options);
419
+ }
420
+ }
421
+ // Group functions
422
+ async getGroups(ncid = this.ncid) {
423
+ if (this.worker) {
424
+ return this.callWorker('getGroups', { ncid });
425
+ }
426
+ else {
427
+ return NCGet.getGroups(this.module, ncid);
428
+ }
429
+ }
430
+ async getGroupsRecursive(ncid = this.ncid) {
431
+ if (this.worker) {
432
+ return this.callWorker('getGroupsRecursive', { ncid });
433
+ }
434
+ else {
435
+ return NCGet.getGroupsRecursive(this.module, ncid);
436
+ }
437
+ }
438
+ async getGroupNCID(groupPath) {
439
+ if (this.worker) {
440
+ return this.callWorker('getGroupNCID', { groupPath });
441
+ }
442
+ else {
443
+ return NCGet.getGroupNCID(this.module, this.ncid, groupPath);
444
+ }
445
+ }
446
+ async getGroupName(ncid = this.ncid) {
447
+ if (this.worker) {
448
+ return this.callWorker('getGroupName', { ncid });
449
+ }
450
+ else {
451
+ return NCGet.getGroupName(this.module, ncid);
452
+ }
453
+ }
454
+ async getGroupPath(ncid = this.ncid) {
455
+ if (this.worker) {
456
+ return this.callWorker('getGroupPath', { ncid });
457
+ }
458
+ else {
459
+ return NCGet.getGroupPath(this.module, ncid);
460
+ }
461
+ }
462
+ async defineDimension(ncid, name, size) {
463
+ const module = this.getModule();
464
+ const result = module.nc_def_dim(ncid, name, size);
465
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
466
+ throw new Error(`Failed to define dimension: ${name} (error: ${result.result})`);
467
+ }
468
+ return result.dimid;
469
+ }
470
+ async defineVariable(ncid, name, type, dimids) {
471
+ const module = this.getModule();
472
+ const result = module.nc_def_var(ncid, name, type, dimids.length, dimids);
473
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
474
+ throw new Error(`Failed to define variable: ${name} (error: ${result.result})`);
475
+ }
476
+ return result.varid;
477
+ }
478
+ async endDefineMode(ncid) {
479
+ const module = this.getModule();
480
+ const result = module.nc_enddef(ncid);
481
+ if (result !== NC_CONSTANTS.NC_NOERR) {
482
+ throw new Error(`Failed to end define mode (error: ${result})`);
483
+ }
484
+ }
485
+ async putVariableDouble(ncid, varid, data) {
486
+ const module = this.getModule();
487
+ const result = module.nc_put_var_double(ncid, varid, data);
488
+ if (result !== NC_CONSTANTS.NC_NOERR) {
489
+ throw new Error(`Failed to write variable data (error: ${result})`);
490
+ }
491
+ }
492
+ async getVariableDouble(ncid, varid, length) {
493
+ const module = this.getModule();
494
+ const result = module.nc_get_var_double(ncid, varid, length);
495
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
496
+ throw new Error(`Failed to read variable data (error: ${result.result})`);
497
+ }
498
+ if (!result.data) {
499
+ throw new Error("nc_get_var_double returned no data");
500
+ }
501
+ return result.data;
502
+ }
503
+ // Create a mock module for testing
504
+ createMockModule() {
505
+ // Global mock file storage to simulate persistence across instances
506
+ if (!global.__netcdf4_mock_files) {
507
+ global.__netcdf4_mock_files = {};
508
+ }
509
+ const mockFiles = global.__netcdf4_mock_files;
510
+ return {
511
+ nc_open: (path, mode) => {
512
+ // Mock implementation that simulates invalid filenames and unsupported modes
513
+ if (!path || path.trim() === '' || path.includes('unsupported') || !['r', 'w', 'a'].some(m => this.mode.includes(m))) {
514
+ return { result: -1, ncid: -1 };
515
+ }
516
+ // For reading mode, file should exist in mock storage, otherwise create a minimal entry
517
+ if (this.mode === 'r' && !mockFiles[path]) {
518
+ // For test purposes, allow reading non-existent files but initialize them empty
519
+ mockFiles[path] = {
520
+ attributes: {},
521
+ dimensions: {},
522
+ variables: {}
523
+ };
524
+ }
525
+ return { result: NC_CONSTANTS.NC_NOERR, ncid: 1 };
526
+ },
527
+ nc_close: (ncid) => {
528
+ // In a real implementation, this would flush data to the file
529
+ // For our mock, we'll keep the data in memory
530
+ return NC_CONSTANTS.NC_NOERR;
531
+ },
532
+ nc_create: (path, mode) => {
533
+ if (path.includes('unsupported') || ['x', 'invalid'].some(m => this.mode.includes(m))) {
534
+ return { result: -1, ncid: -1 };
535
+ }
536
+ // Initialize mock file storage
537
+ mockFiles[path] = {
538
+ attributes: {},
539
+ dimensions: {},
540
+ variables: {}
541
+ };
542
+ return { result: NC_CONSTANTS.NC_NOERR, ncid: 1 };
543
+ },
544
+ nc_def_dim: (ncid, name, len) => {
545
+ // Store dimension in mock file
546
+ if (this.filename && mockFiles[this.filename]) {
547
+ mockFiles[this.filename].dimensions[name] = {
548
+ size: len,
549
+ unlimited: len === NC_CONSTANTS.NC_UNLIMITED
550
+ };
551
+ }
552
+ return { result: NC_CONSTANTS.NC_NOERR, dimid: 1 };
553
+ },
554
+ nc_def_var: (ncid, name, xtype, ndims, dimids) => {
555
+ // Initialize variable storage
556
+ if (this.filename && mockFiles[this.filename]) {
557
+ mockFiles[this.filename].variables[name] = {
558
+ data: new Float64Array(0),
559
+ attributes: {}
560
+ };
561
+ // Return varid based on current variable count (1-based)
562
+ const varCount = Object.keys(mockFiles[this.filename].variables).length;
563
+ return { result: NC_CONSTANTS.NC_NOERR, varid: varCount };
564
+ }
565
+ return { result: NC_CONSTANTS.NC_NOERR, varid: 1 };
566
+ },
567
+ nc_put_var_double: (ncid, varid, data) => {
568
+ // Store data in mock file - try to map varid to variable name
569
+ if (this.filename && mockFiles[this.filename]) {
570
+ const variables = mockFiles[this.filename].variables;
571
+ const varNames = Object.keys(variables);
572
+ // Map varid to variable name (1-based indexing)
573
+ if (varNames.length > 0 && varid >= 1 && varid <= varNames.length) {
574
+ const varName = varNames[varid - 1]; // Convert to 0-based
575
+ variables[varName].data = new Float64Array(data);
576
+ }
577
+ }
578
+ return NC_CONSTANTS.NC_NOERR;
579
+ },
580
+ nc_get_var_double: (ncid, varid, size) => {
581
+ // Try to get actual stored data first
582
+ if (this.filename && mockFiles[this.filename]) {
583
+ const variables = mockFiles[this.filename].variables;
584
+ const varNames = Object.keys(variables);
585
+ // Map varid to variable name (1-based indexing)
586
+ if (varNames.length > 0 && varid >= 1 && varid <= varNames.length) {
587
+ const varName = varNames[varid - 1]; // Convert to 0-based
588
+ const storedData = variables[varName].data;
589
+ if (storedData && storedData.length > 0) {
590
+ // Return the stored data, resized to requested size if needed
591
+ if (size <= 0) {
592
+ return { result: NC_CONSTANTS.NC_NOERR, data: new Float64Array(0) };
593
+ }
594
+ const result = new Float64Array(size);
595
+ for (let i = 0; i < size && i < storedData.length; i++) {
596
+ result[i] = storedData[i];
597
+ }
598
+ return { result: NC_CONSTANTS.NC_NOERR, data: result };
599
+ }
600
+ }
601
+ }
602
+ // Fallback to test pattern if no data stored
603
+ if (size <= 0) {
604
+ return { result: NC_CONSTANTS.NC_NOERR, data: new Float64Array(0) };
605
+ }
606
+ const data = new Float64Array(size);
607
+ for (let i = 0; i < size; i++) {
608
+ data[i] = i * 0.1; // Simple test pattern
609
+ }
610
+ return { result: NC_CONSTANTS.NC_NOERR, data };
611
+ },
612
+ nc_enddef: (ncid) => NC_CONSTANTS.NC_NOERR,
613
+ };
614
+ }
615
+ async setupWorker() {
616
+ if (!this.workerSource)
617
+ throw new Error('No worker source');
618
+ // 1. Instantiate the worker if it doesn't exist
619
+ if (!this.worker) {
620
+ // Option A: If using Vite/Webpack 5
621
+ this.worker = new Worker(new URL('./netcdf-worker.js', import.meta.url), { type: 'module' });
622
+ }
623
+ this.workerReady = new Promise((resolve, reject) => {
624
+ // Use a named function so we can remove the listener later
625
+ const initHandler = (e) => {
626
+ if (e.data.success) {
627
+ this.worker.removeEventListener('message', initHandler);
628
+ resolve();
629
+ }
630
+ else {
631
+ this.worker.removeEventListener('message', initHandler);
632
+ reject(new Error(e.data.message));
633
+ }
634
+ };
635
+ this.worker.addEventListener('message', initHandler);
636
+ // 3. Send the initialization message
637
+ this.worker.postMessage({
638
+ type: 'init',
639
+ blob: this.workerSource.blob,
640
+ filename: this.workerSource.filename
641
+ });
642
+ });
643
+ return this.workerReady;
644
+ }
645
+ // Mount memory data in the WASM virtual file system
646
+ async mountMemoryData() {
647
+ if (!this.memorySource || !this.module) {
648
+ return;
649
+ }
650
+ // Skip mounting in test mode (mock module doesn't have FS)
651
+ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
652
+ return;
653
+ }
654
+ try {
655
+ const module = this.getModule();
656
+ if (!module.FS) {
657
+ throw new Error('Emscripten FS not available');
658
+ }
659
+ // Ensure the /tmp directory exists
660
+ try {
661
+ module.FS.mkdir('/tmp');
662
+ }
663
+ catch (e) {
664
+ // Directory might already exist, ignore error
665
+ }
666
+ // Write the memory data to a virtual file
667
+ module.FS.writeFile(this.memorySource.filename, this.memorySource.data);
668
+ }
669
+ catch (error) {
670
+ throw new Error(`Failed to mount memory data: ${error}`);
671
+ }
672
+ }
673
+ // Get data from memory or file as ArrayBuffer (for writing back to Blob)
674
+ async toArrayBuffer() {
675
+ if (!this.module) {
676
+ throw new Error('NetCDF4 module not initialized');
677
+ }
678
+ // Skip in test mode
679
+ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
680
+ // Return empty buffer in test mode
681
+ return new ArrayBuffer(0);
682
+ }
683
+ try {
684
+ const module = this.getModule();
685
+ if (!module.FS || !this.filename) {
686
+ throw new Error('Cannot read file data');
687
+ }
688
+ // Read the file from the virtual file system
689
+ const data = module.FS.readFile(this.filename);
690
+ return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
691
+ }
692
+ catch (error) {
693
+ throw new Error(`Failed to read data as ArrayBuffer: ${error}`);
694
+ }
695
+ }
696
+ // Convert to Blob
697
+ async toBlob(type = 'application/x-netcdf') {
698
+ const buffer = await this.toArrayBuffer();
699
+ return new Blob([buffer], { type });
700
+ }
701
+ toString() {
702
+ const status = this._isOpen ? 'open' : 'closed';
703
+ const source = this.memorySource ? '(in-memory)' : '';
704
+ return `<netCDF4.Dataset '${this.filename}'${source}: mode = '${this.mode}', file format = '${this.file_format}', ${status}>`;
705
+ }
706
+ /**
707
+ * Get complete hierarchy of groups, variables, dimensions, and attributes
708
+ * This is the unified method for exploring the entire file structure
709
+ * @param groupPath - Optional path to start from a specific group
710
+ */
711
+ async getCompleteHierarchy(groupPath) {
712
+ if (this.worker) {
713
+ return this.callWorker('getCompleteHierarchy', { groupPath });
714
+ }
715
+ else {
716
+ return NCGet.getCompleteHierarchy(this.module, this.ncid, groupPath);
717
+ }
718
+ }
719
+ /**
720
+ * Get all variables recursively from all groups
721
+ * Returns a flat dictionary with full path keys like "/group1/var1"
722
+ */
723
+ async getVariables() {
724
+ if (this.worker) {
725
+ return this.callWorker('getVariables');
726
+ }
727
+ else {
728
+ return NCGet.getVariables(this.module, this.ncid);
729
+ }
730
+ }
731
+ }
732
+ /**
733
+ * UI-friendly wrapper around NetCDF4
734
+ * Builds a full dataTree of groups, variables, attributes with enhanced navigation
735
+ */
736
+ export class DataTree {
737
+ dataset;
738
+ tree = {};
739
+ groupTreeCache = null;
740
+ constructor(dataset) {
741
+ this.dataset = dataset;
742
+ }
743
+ async buildTree() {
744
+ this.tree = await this.dataset.getCompleteHierarchy();
745
+ // Clear cache when tree is rebuilt
746
+ this.groupTreeCache = null;
747
+ }
748
+ // --------------------------------------------------
749
+ // Core navigation
750
+ // --------------------------------------------------
751
+ getGroup(groupPath = '/') {
752
+ if (!this.tree)
753
+ return null;
754
+ if (groupPath === '/' || !groupPath)
755
+ return this.tree;
756
+ const parts = groupPath.split('/').filter(Boolean);
757
+ let current = this.tree;
758
+ for (const part of parts) {
759
+ if (!current.groups || !current.groups[part])
760
+ return null;
761
+ current = current.groups[part];
762
+ }
763
+ return current;
764
+ }
765
+ getGroupName(groupPath) {
766
+ if (!groupPath || groupPath === '/')
767
+ return 'root';
768
+ const parts = groupPath.split('/').filter(Boolean);
769
+ return parts[parts.length - 1];
770
+ }
771
+ hasSubgroups(groupPath = '/') {
772
+ const group = this.getGroup(groupPath);
773
+ return group ? Object.keys(group.groups || {}).length > 0 : false;
774
+ }
775
+ // --------------------------------------------------
776
+ // Groups (for dropdowns)
777
+ // --------------------------------------------------
778
+ /** immediate children only */
779
+ listGroups(groupPath = '/') {
780
+ const group = this.getGroup(groupPath);
781
+ if (!group || !group.groups)
782
+ return [];
783
+ return Object.entries(group.groups).map(([name, g]) => ({
784
+ name,
785
+ path: g.path || `${groupPath === '/' ? '' : groupPath}/${name}`
786
+ }));
787
+ }
788
+ /** every group recursively */
789
+ listAllGroups() {
790
+ const result = [];
791
+ const walk = (g) => {
792
+ if (!g.groups)
793
+ return;
794
+ for (const [name, sub] of Object.entries(g.groups)) {
795
+ result.push({ name, path: sub.path });
796
+ walk(sub);
797
+ }
798
+ };
799
+ walk(this.tree);
800
+ return result;
801
+ }
802
+ // --------------------------------------------------
803
+ // Hierarchical Group Tree
804
+ // --------------------------------------------------
805
+ /**
806
+ * Build a hierarchical tree structure of all groups
807
+ * Returns a tree with parent-child relationships
808
+ * Results are cached until buildTree() is called again
809
+ */
810
+ buildGroupTree() {
811
+ // Return cached version if available
812
+ if (this.groupTreeCache) {
813
+ return this.groupTreeCache;
814
+ }
815
+ const allGroups = this.listAllGroups();
816
+ // Create root node
817
+ const root = {
818
+ name: '/',
819
+ path: '/',
820
+ children: [],
821
+ hasVariables: this.hasVariables('/'),
822
+ hasAttributes: this.hasAttributes('/'),
823
+ variableCount: this.getVariableCount('/'),
824
+ attributeCount: this.getAttributeCount('/')
825
+ };
826
+ // Map to store all nodes by path for quick lookup
827
+ const nodeMap = new Map();
828
+ nodeMap.set('/', root);
829
+ // Sort groups by path depth to ensure parents are created before children
830
+ const sortedGroups = allGroups.sort((a, b) => {
831
+ const depthA = a.path.split('/').filter(Boolean).length;
832
+ const depthB = b.path.split('/').filter(Boolean).length;
833
+ return depthA - depthB;
834
+ });
835
+ // Build the tree
836
+ for (const { name, path } of sortedGroups) {
837
+ const node = {
838
+ name,
839
+ path,
840
+ children: [],
841
+ hasVariables: this.hasVariables(path),
842
+ hasAttributes: this.hasAttributes(path),
843
+ variableCount: this.getVariableCount(path),
844
+ attributeCount: this.getAttributeCount(path)
845
+ };
846
+ nodeMap.set(path, node);
847
+ // Find parent path
848
+ const pathParts = path.split('/').filter(Boolean);
849
+ const parentPath = pathParts.length === 1
850
+ ? '/'
851
+ : '/' + pathParts.slice(0, -1).join('/');
852
+ const parent = nodeMap.get(parentPath);
853
+ if (parent) {
854
+ parent.children.push(node);
855
+ }
856
+ }
857
+ // Cache the result
858
+ this.groupTreeCache = root;
859
+ return root;
860
+ }
861
+ /**
862
+ * Get a specific node from the group tree by path
863
+ */
864
+ getGroupNode(path) {
865
+ const tree = this.buildGroupTree();
866
+ if (path === '/')
867
+ return tree;
868
+ const parts = path.split('/').filter(Boolean);
869
+ let current = tree;
870
+ for (const part of parts) {
871
+ const child = current.children.find(c => c.name === part);
872
+ if (!child)
873
+ return null;
874
+ current = child;
875
+ }
876
+ return current;
877
+ }
878
+ /**
879
+ * Get breadcrumb trail for a given path
880
+ * Returns array of {name, path} from root to target
881
+ */
882
+ getBreadcrumbs(groupPath) {
883
+ if (groupPath === '/') {
884
+ return [{ name: 'root', path: '/' }];
885
+ }
886
+ const parts = groupPath.split('/').filter(Boolean);
887
+ const breadcrumbs = [
888
+ { name: 'root', path: '/' }
889
+ ];
890
+ let currentPath = '';
891
+ for (const part of parts) {
892
+ currentPath += '/' + part;
893
+ breadcrumbs.push({
894
+ name: part,
895
+ path: currentPath
896
+ });
897
+ }
898
+ return breadcrumbs;
899
+ }
900
+ /**
901
+ * Search for groups by name (case-insensitive)
902
+ */
903
+ searchGroups(query) {
904
+ const lowerQuery = query.toLowerCase();
905
+ return this.listAllGroups().filter(g => g.name.toLowerCase().includes(lowerQuery) ||
906
+ g.path.toLowerCase().includes(lowerQuery));
907
+ }
908
+ // --------------------------------------------------
909
+ // Variables
910
+ // --------------------------------------------------
911
+ /** variables inside a group */
912
+ getAllVariables(groupPath = '/') {
913
+ const group = this.getGroup(groupPath);
914
+ return group?.variables || {};
915
+ }
916
+ /** check if group has variables */
917
+ hasVariables(groupPath = '/') {
918
+ const vars = this.getAllVariables(groupPath);
919
+ return Object.keys(vars).length > 0;
920
+ }
921
+ /** count variables in a group */
922
+ getVariableCount(groupPath = '/') {
923
+ return Object.keys(this.getAllVariables(groupPath)).length;
924
+ }
925
+ /** get variable names as array */
926
+ getVariableNames(groupPath = '/') {
927
+ return Object.keys(this.getAllVariables(groupPath));
928
+ }
929
+ /**
930
+ * Search for variables by name across all groups
931
+ */
932
+ searchVariables(query) {
933
+ const results = [];
934
+ const lowerQuery = query.toLowerCase();
935
+ const searchInGroup = (groupPath) => {
936
+ const vars = this.getAllVariables(groupPath);
937
+ for (const varName of Object.keys(vars)) {
938
+ if (varName.toLowerCase().includes(lowerQuery)) {
939
+ results.push({
940
+ name: varName,
941
+ groupPath,
942
+ path: `${groupPath === '/' ? '' : groupPath}/${varName}`
943
+ });
944
+ }
945
+ }
946
+ // Recurse into subgroups
947
+ const subgroups = this.listGroups(groupPath);
948
+ for (const { path } of subgroups) {
949
+ searchInGroup(path);
950
+ }
951
+ };
952
+ searchInGroup('/');
953
+ return results;
954
+ }
955
+ // --------------------------------------------------
956
+ // Attributes
957
+ // --------------------------------------------------
958
+ /** attributes from the tree (fast) */
959
+ getAttributes(groupPath = '/') {
960
+ const group = this.getGroup(groupPath);
961
+ return group?.attributes || {};
962
+ }
963
+ /** check if group has attributes */
964
+ hasAttributes(groupPath = '/') {
965
+ const attrs = this.getAttributes(groupPath);
966
+ return Object.keys(attrs).length > 0;
967
+ }
968
+ /** count attributes in a group */
969
+ getAttributeCount(groupPath = '/') {
970
+ return Object.keys(this.getAttributes(groupPath)).length;
971
+ }
972
+ // --------------------------------------------------
973
+ // Dimensions
974
+ // --------------------------------------------------
975
+ /** get dimensions for a group */
976
+ getDimensions(groupPath = '/') {
977
+ const group = this.getGroup(groupPath);
978
+ return group?.dimensions || {};
979
+ }
980
+ /** check if group has dimensions */
981
+ hasDimensions(groupPath = '/') {
982
+ const dims = this.getDimensions(groupPath);
983
+ return Object.keys(dims).length > 0;
984
+ }
985
+ /** count dimensions in a group */
986
+ getDimensionCount(groupPath = '/') {
987
+ return Object.keys(this.getDimensions(groupPath)).length;
988
+ }
989
+ // --------------------------------------------------
990
+ // Statistics and Summaries
991
+ // --------------------------------------------------
992
+ /**
993
+ * Get summary statistics for a group
994
+ */
995
+ getGroupSummary(groupPath = '/') {
996
+ const group = this.getGroup(groupPath);
997
+ if (!group)
998
+ return null;
999
+ return {
1000
+ path: groupPath,
1001
+ name: this.getGroupName(groupPath),
1002
+ variableCount: this.getVariableCount(groupPath),
1003
+ attributeCount: this.getAttributeCount(groupPath),
1004
+ dimensionCount: this.getDimensionCount(groupPath),
1005
+ subgroupCount: this.listGroups(groupPath).length,
1006
+ hasSubgroups: this.hasSubgroups(groupPath)
1007
+ };
1008
+ }
1009
+ /**
1010
+ * Get complete statistics for the entire dataset
1011
+ */
1012
+ getDatasetSummary() {
1013
+ let totalVariables = 0;
1014
+ let totalAttributes = 0;
1015
+ let totalDimensions = 0;
1016
+ let maxDepth = 0;
1017
+ const countInGroup = (groupPath, depth) => {
1018
+ totalVariables += this.getVariableCount(groupPath);
1019
+ totalAttributes += this.getAttributeCount(groupPath);
1020
+ totalDimensions += this.getDimensionCount(groupPath);
1021
+ maxDepth = Math.max(maxDepth, depth);
1022
+ const subgroups = this.listGroups(groupPath);
1023
+ for (const { path } of subgroups) {
1024
+ countInGroup(path, depth + 1);
1025
+ }
1026
+ };
1027
+ countInGroup('/', 0);
1028
+ return {
1029
+ totalGroups: this.listAllGroups().length + 1, // +1 for root
1030
+ totalVariables,
1031
+ totalAttributes,
1032
+ totalDimensions,
1033
+ maxDepth
1034
+ };
1035
+ }
1036
+ // --------------------------------------------------
1037
+ // Heavy operations → still go to dataset
1038
+ // --------------------------------------------------
1039
+ async getVariableArray(variable, groupPath) {
1040
+ return this.dataset.getVariableArray(variable, groupPath);
1041
+ }
1042
+ async getSlicedVariableArray(variable, start, count, groupPath) {
1043
+ return this.dataset.getSlicedVariableArray(variable, start, count, groupPath);
1044
+ }
1045
+ async getVariableInfo(variable, groupPath) {
1046
+ return this.dataset.getVariableInfo(variable, groupPath);
1047
+ }
1048
+ }
1049
+ //# sourceMappingURL=netcdf4.js.map