@earthyscience/netcdf4-wasm 0.1.2 → 0.2.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.
@@ -1,18 +1,21 @@
1
1
  import { NC_CONSTANTS, DATA_TYPE_SIZE, CONSTANT_DTYPE_MAP } from './constants.js';
2
- export function getVariables(module, ncid) {
2
+ export function getGroupVariables(module, ncid, groupPath) {
3
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
3
4
  const variables = {};
4
- const varCount = getVarCount(module, ncid);
5
- const dimIds = getDimIDs(module, ncid);
5
+ const varCount = getVarCount(module, workingNcid);
6
6
  for (let varid = 0; varid < varCount; varid++) {
7
- if (dimIds.includes(varid))
8
- continue; //Don't include spatial Vars
9
- const result = module.nc_inq_varname(ncid, varid);
10
- if (result.result !== NC_CONSTANTS.NC_NOERR || !result.name) {
11
- console.warn(`Failed to get variable name for varid ${varid} (error: ${result.result})`);
7
+ const nameResult = module.nc_inq_varname(workingNcid, varid);
8
+ if (nameResult.result !== NC_CONSTANTS.NC_NOERR || !nameResult.name) {
9
+ console.warn(`Failed to get variable name for varid ${varid} (error: ${nameResult.result})`);
12
10
  continue;
13
11
  }
14
- variables[result.name] = {
15
- id: varid
12
+ // A coordinate variable is one whose name matches a dimension.
13
+ const isCoordinate = findDimInHierarchy(module, workingNcid, nameResult.name);
14
+ if (isCoordinate)
15
+ continue;
16
+ variables[nameResult.name] = {
17
+ id: varid,
18
+ ncid: workingNcid // CRITICAL: Store the ncid where this variable lives!
16
19
  };
17
20
  }
18
21
  return variables;
@@ -31,73 +34,104 @@ export function getDimCount(module, ncid) {
31
34
  }
32
35
  return result.ndims || 0;
33
36
  }
34
- export function getDims(module, ncid) {
35
- const dimIDs = getDimIDs(module, ncid);
37
+ export function getDims(module, ncid, groupPath) {
38
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
39
+ const dimIDs = getDimIDs(module, workingNcid);
36
40
  const dims = {};
37
41
  for (const dimid of dimIDs) {
38
- const dim = getDim(module, ncid, dimid);
42
+ const dim = getDim(module, workingNcid, dimid);
39
43
  dims[dim.name] = {
40
44
  size: dim.len,
41
- units: dim.units,
45
+ units: dim.units ?? null,
42
46
  id: dim.id
43
47
  };
44
48
  }
45
49
  return dims;
46
50
  }
47
- export function getDimIDs(module, ncid) {
48
- const result = module.nc_inq_dimids(ncid, 0);
51
+ export function getDimIDs(module, ncid, includeParents = false) {
52
+ const result = module.nc_inq_dimids(ncid, includeParents ? 1 : 0);
49
53
  if (result.result !== NC_CONSTANTS.NC_NOERR) {
50
54
  throw new Error(`Failed to get dimension IDs (error: ${result.result})`);
51
55
  }
52
- return result.dimids || [0];
56
+ return result.dimids ?? [];
57
+ }
58
+ function findCoordinateVariable(module, startNcid, name) {
59
+ let current = startNcid;
60
+ while (current !== null) {
61
+ const result = module.nc_inq_varid(current, name);
62
+ if (result.result === NC_CONSTANTS.NC_NOERR) {
63
+ return {
64
+ ncid: current,
65
+ varid: result.varid
66
+ };
67
+ }
68
+ current = getGroupParent(module, current);
69
+ }
70
+ return null;
53
71
  }
54
72
  export function getDim(module, ncid, dimid) {
55
73
  const result = module.nc_inq_dim(ncid, dimid);
56
74
  if (result.result !== NC_CONSTANTS.NC_NOERR) {
57
75
  throw new Error(`Failed to get dim (error: ${result.result})`);
58
76
  }
59
- const varResult = module.nc_inq_varid(ncid, result.name);
60
- const varID = varResult.varid;
61
- const { result: output, ...dim } = result;
62
- const unitResult = getAttributeValues(module, ncid, varID, "units");
63
- return { ...dim, units: unitResult, id: varID };
77
+ const { result: _r, ...dim } = result;
78
+ let varID = null;
79
+ let units = null;
80
+ let coordNcid = null;
81
+ // Search upward for coordinate variable
82
+ const coord = findCoordinateVariable(module, ncid, dim.name);
83
+ if (coord) {
84
+ varID = coord.varid;
85
+ coordNcid = coord.ncid;
86
+ units = getAttributeValues(module, coordNcid, varID, "units");
87
+ }
88
+ return {
89
+ ...dim,
90
+ id: varID,
91
+ units,
92
+ coordNcid // useful for debugging
93
+ };
64
94
  }
65
95
  export function getAttributeValues(module, ncid, varid, attname) {
66
96
  const attInfo = module.nc_inq_att(ncid, varid, attname);
67
97
  if (attInfo.result !== NC_CONSTANTS.NC_NOERR) {
68
98
  console.warn(`Failed to get attribute info for ${attname} (error: ${attInfo.result})`);
69
- return;
99
+ return null;
70
100
  }
71
101
  const attType = attInfo.type;
72
102
  if (!attType)
73
103
  throw new Error("Failed to allocate memory for attribute type.");
74
- let attValue;
75
- if (attType === 2)
76
- attValue = module.nc_get_att_text(ncid, varid, attname, attInfo.len);
77
- else if (attType === 3)
78
- attValue = module.nc_get_att_short(ncid, varid, attname, attInfo.len);
79
- else if (attType === 4)
80
- attValue = module.nc_get_att_int(ncid, varid, attname, attInfo.len);
81
- else if (attType === 5)
82
- attValue = module.nc_get_att_float(ncid, varid, attname, attInfo.len);
83
- else if (attType === 6)
84
- attValue = module.nc_get_att_double(ncid, varid, attname, attInfo.len);
85
- else if (attType === 10)
86
- attValue = module.nc_get_att_longlong(ncid, varid, attname, attInfo.len);
87
- else
88
- attValue = module.nc_get_att_double(ncid, varid, attname, attInfo.len);
104
+ // Dispatch table: maps NetCDF type -> module getter
105
+ const getterMap = {
106
+ [NC_CONSTANTS.NC_CHAR]: module.nc_get_att_text,
107
+ [NC_CONSTANTS.NC_SHORT]: module.nc_get_att_short,
108
+ [NC_CONSTANTS.NC_INT]: module.nc_get_att_int,
109
+ [NC_CONSTANTS.NC_FLOAT]: module.nc_get_att_float,
110
+ [NC_CONSTANTS.NC_DOUBLE]: module.nc_get_att_double,
111
+ [NC_CONSTANTS.NC_UBYTE]: module.nc_get_att_uchar,
112
+ [NC_CONSTANTS.NC_UINT]: module.nc_get_att_uint,
113
+ [NC_CONSTANTS.NC_USHORT]: module.nc_get_att_ushort,
114
+ [NC_CONSTANTS.NC_LONGLONG]: module.nc_get_att_longlong,
115
+ [NC_CONSTANTS.NC_UINT64]: module.nc_get_att_ulonglong,
116
+ [NC_CONSTANTS.NC_STRING]: module.nc_get_att_string
117
+ };
118
+ const getter = getterMap[attType];
119
+ if (!getter)
120
+ throw new Error(`Unsupported attribute type ${attType}`);
121
+ const attValue = getter(ncid, varid, attname, attInfo.len);
89
122
  return attValue.data;
90
123
  }
91
- export function getGlobalAttributes(module, ncid) {
124
+ export function getGlobalAttributes(module, ncid, groupPath) {
125
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
92
126
  const attributes = {};
93
- const nattsResult = module.nc_inq_natts(ncid);
127
+ const nattsResult = module.nc_inq_natts(workingNcid);
94
128
  if (nattsResult.result !== NC_CONSTANTS.NC_NOERR) {
95
129
  throw new Error(`Failed to get number of global attributes (error: ${nattsResult.result})`);
96
130
  }
97
131
  const nAtts = nattsResult.natts;
98
132
  const attNames = [];
99
133
  for (let i = 0; i < nAtts; i++) {
100
- const name = getAttributeName(module, ncid, NC_CONSTANTS.NC_GLOBAL, i);
134
+ const name = getAttributeName(module, workingNcid, NC_CONSTANTS.NC_GLOBAL, i);
101
135
  attNames.push(name);
102
136
  }
103
137
  if (attNames.length === 0)
@@ -105,7 +139,7 @@ export function getGlobalAttributes(module, ncid) {
105
139
  for (const attname of attNames) {
106
140
  if (!attname)
107
141
  continue;
108
- attributes[attname] = getAttributeValues(module, ncid, NC_CONSTANTS.NC_GLOBAL, attname);
142
+ attributes[attname] = getAttributeValues(module, workingNcid, NC_CONSTANTS.NC_GLOBAL, attname);
109
143
  }
110
144
  return attributes;
111
145
  }
@@ -116,11 +150,12 @@ export function getAttributeName(module, ncid, varid, attId) {
116
150
  }
117
151
  return result.name;
118
152
  }
119
- export function getFullMetadata(module, ncid) {
120
- const varIds = getVarIDs(module, ncid);
153
+ export function getFullMetadata(module, ncid, groupPath) {
154
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
155
+ const varIds = getVarIDs(module, workingNcid);
121
156
  const metas = [];
122
157
  for (const varid of varIds) {
123
- const varMeta = getVariableInfo(module, ncid, varid);
158
+ const varMeta = getVariableInfo(module, workingNcid, varid);
124
159
  const { attributes, ...varDeets } = varMeta;
125
160
  metas.push({ ...varDeets, ...attributes });
126
161
  }
@@ -131,39 +166,44 @@ export function getVarIDs(module, ncid) {
131
166
  if (result.result !== NC_CONSTANTS.NC_NOERR) {
132
167
  throw new Error(`Failed to get variable IDs (error: ${result.result})`);
133
168
  }
134
- return result.varids || [0];
169
+ return result.varids ?? [];
135
170
  }
136
- export function getVariableInfo(module, ncid, variable) {
171
+ export function getVariableInfo(module, ncid, variable, groupPath) {
172
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
137
173
  const info = {};
138
174
  const isId = typeof variable === "number";
139
175
  let varid = variable;
140
176
  if (!isId) {
141
- const result = module.nc_inq_varid(ncid, variable);
177
+ const result = module.nc_inq_varid(workingNcid, variable);
142
178
  varid = result.varid;
143
179
  }
144
- const result = module.nc_inq_var(ncid, varid);
180
+ const result = module.nc_inq_var(workingNcid, varid);
145
181
  if (result.result !== NC_CONSTANTS.NC_NOERR) {
146
182
  throw new Error(`Failed to get variable info (error: ${result.result})`);
147
183
  }
148
184
  const typeMultiplier = DATA_TYPE_SIZE[result.type];
149
- //Dim Info
185
+ // Dim Info - FIXED to preserve order
150
186
  const dimids = result.dimids;
151
187
  const dims = [];
152
188
  const shape = [];
189
+ const dimensions = []; // Array to store dimension names in order
153
190
  let size = 1;
154
191
  if (dimids) {
155
- for (const dimid of dimids) {
156
- const dim = getDim(module, ncid, dimid);
192
+ // Iterate through dimids in order - this matches the shape order
193
+ for (let i = 0; i < dimids.length; i++) {
194
+ const dimid = dimids[i];
195
+ const dim = getDim(module, workingNcid, dimid);
157
196
  size *= dim.len;
158
197
  dims.push(dim);
159
198
  shape.push(dim.len);
199
+ dimensions.push(dim.name); // Add dimension name in the same order
160
200
  }
161
201
  }
162
- //Attribute Info
202
+ // Attribute Info
163
203
  const attNames = [];
164
204
  if (result.natts) {
165
205
  for (let i = 0; i < result.natts; i++) {
166
- const attname = getAttributeName(module, ncid, varid, i);
206
+ const attname = getAttributeName(module, workingNcid, varid, i);
167
207
  attNames.push(attname);
168
208
  }
169
209
  }
@@ -172,12 +212,12 @@ export function getVariableInfo(module, ncid, variable) {
172
212
  for (const attname of attNames) {
173
213
  if (!attname)
174
214
  continue;
175
- atts[attname] = getAttributeValues(module, ncid, varid, attname);
215
+ atts[attname] = getAttributeValues(module, workingNcid, varid, attname);
176
216
  }
177
217
  }
178
- //Chunking Info
218
+ // Chunking Info
179
219
  let chunks;
180
- const chunkResult = module.nc_inq_var_chunking(ncid, varid);
220
+ const chunkResult = module.nc_inq_var_chunking(workingNcid, varid);
181
221
  const isChunked = chunkResult.chunking === NC_CONSTANTS.NC_CHUNKED;
182
222
  if (isChunked) {
183
223
  chunks = chunkResult.chunkSizes;
@@ -186,12 +226,13 @@ export function getVariableInfo(module, ncid, variable) {
186
226
  chunks = shape;
187
227
  }
188
228
  const chunkElements = chunks.reduce((a, b) => a * b, 1);
189
- //Output
229
+ // Output
190
230
  info["name"] = result.name;
191
231
  info["dtype"] = CONSTANT_DTYPE_MAP[result.type];
192
232
  info['nctype'] = result.type;
193
233
  info["shape"] = shape;
194
234
  info['dims'] = dims;
235
+ info["dimensions"] = dimensions; // Add ordered dimension names array
195
236
  info["size"] = size;
196
237
  info["totalSize"] = size * typeMultiplier;
197
238
  info["attributes"] = atts;
@@ -200,64 +241,360 @@ export function getVariableInfo(module, ncid, variable) {
200
241
  info["chunkSize"] = chunkElements * typeMultiplier;
201
242
  return info;
202
243
  }
203
- export function getVariableArray(module, ncid, variable) {
204
- const isId = typeof variable === "number";
205
- let varid = isId ? variable : 0;
206
- if (!isId) {
207
- const result = module.nc_inq_varid(ncid, variable);
244
+ export function getVariableArray(module, ncid, variable, groupPath) {
245
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
246
+ // Resolve variable id
247
+ let varid;
248
+ if (typeof variable === "number") {
249
+ varid = variable;
250
+ }
251
+ else {
252
+ const result = module.nc_inq_varid(workingNcid, variable);
253
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
254
+ throw new Error(`Failed to get variable id for '${variable}' (error: ${result.result})`);
255
+ }
208
256
  varid = result.varid;
209
257
  }
210
- const info = getVariableInfo(module, ncid, varid);
211
- const arraySize = info.size;
258
+ const info = getVariableInfo(module, workingNcid, varid);
212
259
  const arrayType = info.nctype;
213
- if (!arrayType || !arraySize)
214
- throw new Error("Failed to allocate memory for array");
260
+ const arraySize = info.size;
261
+ if (arrayType === undefined || arrayType === null) {
262
+ throw new Error("Failed to determine variable type");
263
+ }
264
+ if (arraySize === undefined || arraySize === null) {
265
+ throw new Error("Failed to determine variable size");
266
+ }
267
+ // Arrow wrappers = safe binding
268
+ const readers = {
269
+ [NC_CONSTANTS.NC_CHAR]: (...args) => module.nc_get_var_text(...args),
270
+ [NC_CONSTANTS.NC_SHORT]: (...args) => module.nc_get_var_short(...args),
271
+ [NC_CONSTANTS.NC_INT]: (...args) => module.nc_get_var_int(...args),
272
+ [NC_CONSTANTS.NC_FLOAT]: (...args) => module.nc_get_var_float(...args),
273
+ [NC_CONSTANTS.NC_DOUBLE]: (...args) => module.nc_get_var_double(...args),
274
+ [NC_CONSTANTS.NC_LONGLONG]: (...args) => module.nc_get_var_longlong(...args),
275
+ [NC_CONSTANTS.NC_BYTE]: (...args) => module.nc_get_var_schar(...args),
276
+ [NC_CONSTANTS.NC_UBYTE]: (...args) => module.nc_get_var_uchar(...args),
277
+ [NC_CONSTANTS.NC_USHORT]: (...args) => module.nc_get_var_ushort(...args),
278
+ [NC_CONSTANTS.NC_UINT]: (...args) => module.nc_get_var_uint(...args),
279
+ [NC_CONSTANTS.NC_INT64]: (...args) => module.nc_get_var_longlong(...args),
280
+ [NC_CONSTANTS.NC_UINT64]: (...args) => module.nc_get_var_ulonglong(...args),
281
+ [NC_CONSTANTS.NC_STRING]: (...args) => module.nc_get_var_string(...args),
282
+ };
283
+ const reader = readers[arrayType];
215
284
  let arrayData;
216
- if (arrayType === 2)
217
- arrayData = module.nc_get_var_text(ncid, varid, arraySize);
218
- else if (arrayType === 3)
219
- arrayData = module.nc_get_var_short(ncid, varid, arraySize);
220
- else if (arrayType === 4)
221
- arrayData = module.nc_get_var_int(ncid, varid, arraySize);
222
- else if (arrayType === 10)
223
- arrayData = module.nc_get_var_longlong(ncid, varid, arraySize);
224
- else if (arrayType === 5)
225
- arrayData = module.nc_get_var_float(ncid, varid, arraySize);
226
- else if (arrayType === 6)
227
- arrayData = module.nc_get_var_double(ncid, varid, arraySize);
228
- else
229
- arrayData = module.nc_get_var_double(ncid, varid, arraySize);
230
- if (!arrayData.data)
231
- throw new Error("Failed to read array data");
285
+ if (!reader) {
286
+ console.warn(`Unknown NetCDF type ${arrayType}, falling back to double`);
287
+ arrayData = module.nc_get_var_double(workingNcid, varid, arraySize);
288
+ }
289
+ else {
290
+ arrayData = reader(workingNcid, varid, arraySize);
291
+ }
292
+ if (arrayData.result !== NC_CONSTANTS.NC_NOERR) {
293
+ throw new Error(`Failed to read array data (error: ${arrayData.result})`);
294
+ }
295
+ if (!arrayData.data) {
296
+ console.error("nc_get_var result:", arrayData);
297
+ throw new Error("nc_get_var returned no data");
298
+ }
232
299
  return arrayData.data;
233
300
  }
234
- export function getSlicedVariableArray(module, ncid, variable, start, count) {
235
- const isId = typeof variable === "number";
236
- let varid = isId ? variable : 0;
237
- if (!isId) {
238
- const result = module.nc_inq_varid(ncid, variable);
301
+ export function getSlicedVariableArray(module, ncid, variable, start, count, groupPath) {
302
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
303
+ // Resolve variable id
304
+ let varid;
305
+ if (typeof variable === "number") {
306
+ varid = variable;
307
+ }
308
+ else {
309
+ const result = module.nc_inq_varid(workingNcid, variable);
310
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
311
+ throw new Error(`Failed to get variable id for '${variable}' (error: ${result.result})`);
312
+ }
239
313
  varid = result.varid;
240
314
  }
241
- const info = getVariableInfo(module, ncid, varid);
315
+ const info = getVariableInfo(module, workingNcid, varid);
242
316
  const arrayType = info.nctype;
243
- if (!arrayType)
244
- throw new Error("Failed to allocate memory for array");
317
+ if (arrayType === undefined || arrayType === null) {
318
+ throw new Error("Failed to determine variable type");
319
+ }
320
+ // Arrow wrappers keep module binding intact
321
+ const readers = {
322
+ [NC_CONSTANTS.NC_SHORT]: (...args) => module.nc_get_vara_short(...args),
323
+ [NC_CONSTANTS.NC_INT]: (...args) => module.nc_get_vara_int(...args),
324
+ [NC_CONSTANTS.NC_FLOAT]: (...args) => module.nc_get_vara_float(...args),
325
+ [NC_CONSTANTS.NC_DOUBLE]: (...args) => module.nc_get_vara_double(...args),
326
+ [NC_CONSTANTS.NC_BYTE]: (...args) => module.nc_get_vara_schar(...args),
327
+ [NC_CONSTANTS.NC_UBYTE]: (...args) => module.nc_get_vara_uchar(...args),
328
+ [NC_CONSTANTS.NC_USHORT]: (...args) => module.nc_get_vara_ushort(...args),
329
+ [NC_CONSTANTS.NC_UINT]: (...args) => module.nc_get_vara_uint(...args),
330
+ [NC_CONSTANTS.NC_INT64]: (...args) => module.nc_get_vara_longlong(...args),
331
+ [NC_CONSTANTS.NC_UINT64]: (...args) => module.nc_get_vara_ulonglong(...args),
332
+ [NC_CONSTANTS.NC_STRING]: (...args) => module.nc_get_vara_string(...args),
333
+ };
334
+ const reader = readers[arrayType];
245
335
  let arrayData;
246
- if (arrayType === 3)
247
- arrayData = module.nc_get_vara_short(ncid, varid, start, count);
248
- else if (arrayType === 4)
249
- arrayData = module.nc_get_vara_int(ncid, varid, start, count);
250
- else if (arrayType === 5)
251
- arrayData = module.nc_get_vara_float(ncid, varid, start, count);
252
- else if (arrayType === 6)
253
- arrayData = module.nc_get_vara_double(ncid, varid, start, count);
254
- else
255
- arrayData = module.nc_get_vara_double(ncid, varid, start, count);
336
+ if (!reader) {
337
+ console.warn(`Unknown NetCDF type ${arrayType}, falling back to double`);
338
+ arrayData = module.nc_get_vara_double(workingNcid, varid, start, count);
339
+ }
340
+ else {
341
+ arrayData = reader(workingNcid, varid, start, count);
342
+ }
343
+ if (arrayData.result !== NC_CONSTANTS.NC_NOERR) {
344
+ throw new Error(`Failed to read sliced array data (error: ${arrayData.result})`);
345
+ }
256
346
  if (!arrayData.data) {
257
- console.log(arrayData);
258
- throw new Error("Failed to read array data");
347
+ console.error("nc_get_vara result:", arrayData);
348
+ throw new Error("Failed to read array data - no data returned");
259
349
  }
260
350
  return arrayData.data;
261
351
  }
262
- // if (!module) throw new Error("Failed to load module. Ensure module is initialized before calling methods")
352
+ //---- Group Functions ----//
353
+ /**
354
+ * Get group ncid by path (supports nested groups)
355
+ * Uses nc_inq_grp_full_ncid for absolute paths and manual traversal for relative paths
356
+ * @param module - NetCDF4 module
357
+ * @param ncid - Current ncid (can be root or any group)
358
+ * @param groupPath - Can be absolute ("/group1/subgroup") or relative ("subgroup" or "group1/subgroup")
359
+ * @returns The ncid of the requested group
360
+ */
361
+ export function getGroupNCID(module, ncid, groupPath) {
362
+ // Optimization: if path is root, return the ncid
363
+ if (groupPath === '/' || groupPath === '') {
364
+ return ncid;
365
+ }
366
+ // Try using nc_inq_grp_full_ncid for absolute paths (more efficient)
367
+ if (groupPath.startsWith('/')) {
368
+ const result = module.nc_inq_grp_full_ncid(ncid, groupPath);
369
+ if (result.result === NC_CONSTANTS.NC_NOERR) {
370
+ return result.grp_ncid;
371
+ }
372
+ // Get current path for better error message
373
+ const currentPath = getGroupPath(module, ncid);
374
+ throw new Error(`Failed to find group '${groupPath}' from '${currentPath}' (error: ${result.result})`);
375
+ }
376
+ // Manual traversal for relative paths
377
+ const parts = groupPath.split('/').filter(p => p.length > 0);
378
+ let currentNcid = ncid;
379
+ for (const part of parts) {
380
+ const result = module.nc_inq_grp_ncid(currentNcid, part);
381
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
382
+ const currentPath = getGroupPath(module, currentNcid);
383
+ throw new Error(`Failed to get group ncid for '${part}' in path '${groupPath}' from '${currentPath}' (error: ${result.result})`);
384
+ }
385
+ currentNcid = result.grp_ncid;
386
+ }
387
+ return currentNcid;
388
+ }
389
+ /**
390
+ * Alias for getGroupNCID (matches nc_inq_ncid API)
391
+ * @param module - NetCDF4 module
392
+ * @param ncid - Current ncid
393
+ * @param groupName - Group name or path
394
+ * @returns The ncid of the requested group
395
+ */
396
+ export const getNCID = getGroupNCID;
397
+ /**
398
+ * Get immediate child groups (non-recursive)
399
+ * @param module - NetCDF4 module
400
+ * @param ncid - Group ncid to query
401
+ * @returns Object mapping group names to their ncids
402
+ */
403
+ export function getGroups(module, ncid) {
404
+ const result = module.nc_inq_grps(ncid);
405
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
406
+ throw new Error(`Failed to get groups (error: ${result.result})`);
407
+ }
408
+ const groups = {};
409
+ const grpids = result.grpids || [];
410
+ for (const grpid of grpids) {
411
+ const nameResult = module.nc_inq_grpname(grpid);
412
+ if (nameResult.result === NC_CONSTANTS.NC_NOERR && nameResult.name) {
413
+ groups[nameResult.name] = grpid;
414
+ }
415
+ }
416
+ return groups;
417
+ }
418
+ /**
419
+ * Get all groups recursively (returns nested structure)
420
+ * @param module - NetCDF4 module
421
+ * @param ncid - Group ncid to start from (usually root)
422
+ * @returns Nested object structure with group names, ncids, and subgroups
423
+ */
424
+ export function getGroupsRecursive(module, ncid) {
425
+ const result = module.nc_inq_grps(ncid);
426
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
427
+ throw new Error(`Failed to get groups (error: ${result.result})`);
428
+ }
429
+ const groups = {};
430
+ const grpids = result.grpids || [];
431
+ for (const grpid of grpids) {
432
+ const nameResult = module.nc_inq_grpname(grpid);
433
+ if (nameResult.result === NC_CONSTANTS.NC_NOERR && nameResult.name) {
434
+ groups[nameResult.name] = {
435
+ ncid: grpid,
436
+ subgroups: getGroupsRecursive(module, grpid) // Recursive call
437
+ };
438
+ }
439
+ }
440
+ return groups;
441
+ }
442
+ /**
443
+ * Get the name of a group
444
+ * @param module - NetCDF4 module
445
+ * @param ncid - Group ncid
446
+ * @returns Group name
447
+ */
448
+ export function getGroupName(module, ncid) {
449
+ const result = module.nc_inq_grpname(ncid);
450
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
451
+ throw new Error(`Failed to get group name (error: ${result.result})`);
452
+ }
453
+ return result.name || '';
454
+ }
455
+ /**
456
+ * Get the full absolute path of a group
457
+ * Uses nc_inq_grpname_full for efficient path retrieval
458
+ * @param module - NetCDF4 module
459
+ * @param ncid - Group ncid
460
+ * @returns Full path like "/group1/subgroup"
461
+ */
462
+ export function getGroupPath(module, ncid) {
463
+ // Use nc_inq_grpname_full to get the complete path efficiently
464
+ const result = module.nc_inq_grpname_full(ncid);
465
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
466
+ throw new Error(`Failed to get group full name (error: ${result.result})`);
467
+ }
468
+ return result.full_name || '/';
469
+ }
470
+ /**
471
+ * Get the length of a group's full path name
472
+ * @param module - NetCDF4 module
473
+ * @param ncid - Group ncid
474
+ * @returns Length of the full group path name
475
+ */
476
+ export function getGroupPathLength(module, ncid) {
477
+ const result = module.nc_inq_grpname_len(ncid);
478
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
479
+ throw new Error(`Failed to get group name length (error: ${result.result})`);
480
+ }
481
+ return result.lenp || 0;
482
+ }
483
+ /**
484
+ * Get the parent group ncid
485
+ * @param module - NetCDF4 module
486
+ * @param ncid - Group ncid
487
+ * @returns Parent group ncid, or null if this is the root group
488
+ */
489
+ export function getGroupParent(module, ncid) {
490
+ const result = module.nc_inq_grp_parent(ncid);
491
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
492
+ return null; // No parent (at root)
493
+ }
494
+ return result.parent_ncid;
495
+ }
496
+ function findDimInHierarchy(module, startNcid, name) {
497
+ let current = startNcid;
498
+ while (current !== null) {
499
+ const result = module.nc_inq_dimid(current, name);
500
+ if (result.result === NC_CONSTANTS.NC_NOERR) {
501
+ return true;
502
+ }
503
+ current = getGroupParent(module, current);
504
+ }
505
+ return false;
506
+ }
507
+ /**
508
+ * Get complete hierarchy: groups + their variables, dimensions, and attributes recursively
509
+ * This is the unified method that should be used for exploring the file structure
510
+ * @param module - NetCDF4 module
511
+ * @param ncid - Group ncid to start from
512
+ * @param groupPath - Optional path to a specific starting group
513
+ * @param includeParentDims - Whether to include parent dimensions in each group (default: false)
514
+ * @returns Complete hierarchical structure
515
+ */
516
+ export function getCompleteHierarchy(module, ncid, groupPath, includeParentDims = false) {
517
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
518
+ // Get variables at this level (now includes ncid)
519
+ const variables = getGroupVariables(module, workingNcid);
520
+ // Get dimensions at this level
521
+ const dimensions = getDims(module, workingNcid);
522
+ // If includeParentDims is true, also get parent dimensions
523
+ if (includeParentDims) {
524
+ const result = module.nc_inq_dimids(workingNcid, 1);
525
+ if (result.result === NC_CONSTANTS.NC_NOERR && result.dimids) {
526
+ const parentDims = {};
527
+ for (const dimid of result.dimids) {
528
+ const dim = getDim(module, workingNcid, dimid);
529
+ // Only add if not already in dimensions (avoid duplicates)
530
+ if (!dimensions[dim.name]) {
531
+ parentDims[dim.name] = {
532
+ size: dim.len,
533
+ units: dim.units ?? null,
534
+ id: dim.id,
535
+ inherited: true
536
+ };
537
+ }
538
+ }
539
+ // Merge parent dimensions
540
+ Object.assign(dimensions, parentDims);
541
+ }
542
+ }
543
+ // Get attributes at this level
544
+ const attributes = getGlobalAttributes(module, workingNcid);
545
+ // Get the full path for this group
546
+ const fullPath = getGroupPath(module, workingNcid);
547
+ // Get subgroups and recurse
548
+ const groupsResult = module.nc_inq_grps(workingNcid);
549
+ const subgroups = {};
550
+ if (groupsResult.result === NC_CONSTANTS.NC_NOERR && groupsResult.grpids) {
551
+ for (const grpid of groupsResult.grpids) {
552
+ const nameResult = module.nc_inq_grpname(grpid);
553
+ if (nameResult.result === NC_CONSTANTS.NC_NOERR && nameResult.name) {
554
+ // Recursively get the complete hierarchy for this subgroup
555
+ subgroups[nameResult.name] = getCompleteHierarchy(module, grpid, undefined, includeParentDims);
556
+ }
557
+ }
558
+ }
559
+ return {
560
+ ncid: workingNcid, // Include the ncid at this level
561
+ path: fullPath, // Include the full path
562
+ variables,
563
+ dimensions,
564
+ attributes,
565
+ groups: subgroups
566
+ };
567
+ }
568
+ /**
569
+ * Get all variables recursively from all groups
570
+ * Returns a flat structure with full paths as keys
571
+ * @param module - NetCDF4 module
572
+ * @param ncid - Group ncid to start from
573
+ * @param currentPath - Current path (used internally for recursion)
574
+ * @returns Flat dictionary with full variable paths
575
+ */
576
+ export function getVariables(module, ncid, currentPath) {
577
+ const allVars = {};
578
+ // Get the actual path if not provided
579
+ const path = currentPath || getGroupPath(module, ncid);
580
+ // Get variables at current level
581
+ const vars = getGroupVariables(module, ncid);
582
+ for (const [name, varData] of Object.entries(vars)) {
583
+ const fullPath = path === '/' ? `/${name}` : `${path}/${name}`;
584
+ allVars[fullPath] = { ...varData, path, ncid };
585
+ }
586
+ // Recurse into subgroups
587
+ const groupsResult = module.nc_inq_grps(ncid);
588
+ if (groupsResult.result === NC_CONSTANTS.NC_NOERR && groupsResult.grpids) {
589
+ for (const grpid of groupsResult.grpids) {
590
+ const nameResult = module.nc_inq_grpname(grpid);
591
+ if (nameResult.result === NC_CONSTANTS.NC_NOERR && nameResult.name) {
592
+ const newPath = path === '/' ? `/${nameResult.name}` : `${path}/${nameResult.name}`;
593
+ const subVars = getVariables(module, grpid, newPath);
594
+ Object.assign(allVars, subVars);
595
+ }
596
+ }
597
+ }
598
+ return allVars;
599
+ }
263
600
  //# sourceMappingURL=netcdf-getters.js.map