@earthyscience/netcdf4-wasm 0.1.3 → 0.2.1

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,105 @@ 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_BYTE]: module.nc_get_att_schar,
112
+ [NC_CONSTANTS.NC_UBYTE]: module.nc_get_att_uchar,
113
+ [NC_CONSTANTS.NC_UINT]: module.nc_get_att_uint,
114
+ [NC_CONSTANTS.NC_USHORT]: module.nc_get_att_ushort,
115
+ [NC_CONSTANTS.NC_LONGLONG]: module.nc_get_att_longlong,
116
+ [NC_CONSTANTS.NC_UINT64]: module.nc_get_att_ulonglong,
117
+ [NC_CONSTANTS.NC_STRING]: module.nc_get_att_string
118
+ };
119
+ const getter = getterMap[attType];
120
+ if (!getter)
121
+ throw new Error(`Unsupported attribute type ${attType}`);
122
+ const attValue = getter(ncid, varid, attname, attInfo.len);
89
123
  return attValue.data;
90
124
  }
91
- export function getGlobalAttributes(module, ncid) {
125
+ export function getGlobalAttributes(module, ncid, groupPath) {
126
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
92
127
  const attributes = {};
93
- const nattsResult = module.nc_inq_natts(ncid);
128
+ const nattsResult = module.nc_inq_natts(workingNcid);
94
129
  if (nattsResult.result !== NC_CONSTANTS.NC_NOERR) {
95
130
  throw new Error(`Failed to get number of global attributes (error: ${nattsResult.result})`);
96
131
  }
97
132
  const nAtts = nattsResult.natts;
98
133
  const attNames = [];
99
134
  for (let i = 0; i < nAtts; i++) {
100
- const name = getAttributeName(module, ncid, NC_CONSTANTS.NC_GLOBAL, i);
135
+ const name = getAttributeName(module, workingNcid, NC_CONSTANTS.NC_GLOBAL, i);
101
136
  attNames.push(name);
102
137
  }
103
138
  if (attNames.length === 0)
@@ -105,7 +140,7 @@ export function getGlobalAttributes(module, ncid) {
105
140
  for (const attname of attNames) {
106
141
  if (!attname)
107
142
  continue;
108
- attributes[attname] = getAttributeValues(module, ncid, NC_CONSTANTS.NC_GLOBAL, attname);
143
+ attributes[attname] = getAttributeValues(module, workingNcid, NC_CONSTANTS.NC_GLOBAL, attname);
109
144
  }
110
145
  return attributes;
111
146
  }
@@ -116,11 +151,12 @@ export function getAttributeName(module, ncid, varid, attId) {
116
151
  }
117
152
  return result.name;
118
153
  }
119
- export function getFullMetadata(module, ncid) {
120
- const varIds = getVarIDs(module, ncid);
154
+ export function getFullMetadata(module, ncid, groupPath) {
155
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
156
+ const varIds = getVarIDs(module, workingNcid);
121
157
  const metas = [];
122
158
  for (const varid of varIds) {
123
- const varMeta = getVariableInfo(module, ncid, varid);
159
+ const varMeta = getVariableInfo(module, workingNcid, varid);
124
160
  const { attributes, ...varDeets } = varMeta;
125
161
  metas.push({ ...varDeets, ...attributes });
126
162
  }
@@ -131,133 +167,529 @@ export function getVarIDs(module, ncid) {
131
167
  if (result.result !== NC_CONSTANTS.NC_NOERR) {
132
168
  throw new Error(`Failed to get variable IDs (error: ${result.result})`);
133
169
  }
134
- return result.varids || [0];
170
+ return result.varids ?? [];
171
+ }
172
+ export function getEnumType(module, ncid, xtype) {
173
+ // Get basic enum info
174
+ const { result: infoResult, name, baseType, baseSize, numMembers } = module.nc_inq_enum(ncid, xtype);
175
+ if (infoResult !== NC_CONSTANTS.NC_NOERR || !name || baseType === undefined || numMembers === undefined) {
176
+ throw new Error(`Failed to get enum info (error: ${infoResult})`);
177
+ }
178
+ // Get all members
179
+ const members = [];
180
+ for (let i = 0; i < numMembers; i++) {
181
+ const { result, name: memberName, value } = module.nc_inq_enum_member(ncid, xtype, i, baseType);
182
+ if (result === NC_CONSTANTS.NC_NOERR && memberName !== undefined && value !== undefined) {
183
+ members.push({ name: memberName, value });
184
+ }
185
+ }
186
+ return {
187
+ name,
188
+ baseType,
189
+ baseSize: baseSize,
190
+ numMembers,
191
+ members
192
+ };
193
+ }
194
+ export function getTypeClass(module, ncid, xtype) {
195
+ // Atomic types return themselves as the class
196
+ if (xtype < 13) { // Below NC_VLEN
197
+ return xtype;
198
+ }
199
+ // For user-defined types, query the class
200
+ const { result, typeClass } = module.nc_inq_user_type(ncid, xtype);
201
+ if (result === NC_CONSTANTS.NC_NOERR && typeClass !== undefined) {
202
+ return typeClass;
203
+ }
204
+ return xtype;
205
+ }
206
+ function buildEnumDict(module, ncid, enumTypeId, enumBaseType) {
207
+ const enumInqResult = module.nc_inq_enum(ncid, enumTypeId);
208
+ const { result: enumResult, numMembers } = enumInqResult;
209
+ if (enumResult !== NC_CONSTANTS.NC_NOERR || numMembers === undefined) {
210
+ throw new Error(`Failed to get enum info (error: ${enumResult})`);
211
+ }
212
+ const enumDict = {};
213
+ for (let i = 0; i < numMembers; i++) {
214
+ const memberResult = module.nc_inq_enum_member(ncid, enumTypeId, i, enumBaseType);
215
+ const { result: memberResultCode, name: memberName, value } = memberResult;
216
+ if (memberResultCode === NC_CONSTANTS.NC_NOERR && memberName !== undefined && value !== undefined) {
217
+ // Note: bigint values exceeding Number.MAX_SAFE_INTEGER will lose precision.
218
+ // In practice enum values are small named constants so this is acceptable.
219
+ const numValue = typeof value === 'bigint' ? Number(value) : value;
220
+ enumDict[numValue] = memberName;
221
+ }
222
+ else {
223
+ console.warn(`[buildEnumDict] Skipping member ${i}: result=${memberResultCode}, name=${memberName}, value=${value}`);
224
+ }
225
+ }
226
+ return enumDict;
227
+ }
228
+ function resolveEnumContext(module, ncid, varType) {
229
+ const typeClass = getTypeClass(module, ncid, varType);
230
+ if (typeClass === NC_CONSTANTS.NC_VLEN ||
231
+ typeClass === NC_CONSTANTS.NC_OPAQUE ||
232
+ typeClass === NC_CONSTANTS.NC_COMPOUND) {
233
+ throw new Error(`Unsupported type class: ${typeClass} (VLEN, OPAQUE, and COMPOUND not yet implemented)`);
234
+ }
235
+ if (typeClass !== NC_CONSTANTS.NC_ENUM) {
236
+ return { isEnum: false, baseType: varType };
237
+ }
238
+ const { result, baseType } = module.nc_inq_enum(ncid, varType);
239
+ if (result !== NC_CONSTANTS.NC_NOERR || baseType === undefined) {
240
+ throw new Error(`Failed to get enum base type (error: ${result})`);
241
+ }
242
+ const enumDict = buildEnumDict(module, ncid, varType, baseType);
243
+ return { isEnum: true, baseType, enumDict };
244
+ }
245
+ function resolveVariableType(module, ncid, varid) {
246
+ const result = module.nc_inq_var(ncid, varid);
247
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
248
+ throw new Error(`Failed to get variable info (error: ${result.result})`);
249
+ }
250
+ const dimids = result.dimids ? Array.from(result.dimids) : [];
251
+ const size = dimids.reduce((acc, dimid) => acc * getDim(module, ncid, dimid).len, 1);
252
+ const nctype = result.type;
253
+ const enumCtx = resolveEnumContext(module, ncid, nctype);
254
+ return { nctype, size, enumCtx };
135
255
  }
136
- export function getVariableInfo(module, ncid, variable) {
256
+ export function getVariableInfo(module, ncid, variable, groupPath) {
257
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
137
258
  const info = {};
138
259
  const isId = typeof variable === "number";
139
260
  let varid = variable;
140
261
  if (!isId) {
141
- const result = module.nc_inq_varid(ncid, variable);
262
+ const result = module.nc_inq_varid(workingNcid, variable);
142
263
  varid = result.varid;
143
264
  }
144
- const result = module.nc_inq_var(ncid, varid);
265
+ const result = module.nc_inq_var(workingNcid, varid);
145
266
  if (result.result !== NC_CONSTANTS.NC_NOERR) {
146
267
  throw new Error(`Failed to get variable info (error: ${result.result})`);
147
268
  }
148
- const typeMultiplier = DATA_TYPE_SIZE[result.type];
149
- //Dim Info
269
+ const varType = result.type;
270
+ const enumCtx = resolveEnumContext(module, workingNcid, varType);
271
+ const actualType = enumCtx.baseType;
272
+ const typeMultiplier = DATA_TYPE_SIZE[actualType];
273
+ // Dim Info
150
274
  const dimids = result.dimids;
151
275
  const dims = [];
152
276
  const shape = [];
277
+ const dimensions = [];
153
278
  let size = 1;
154
279
  if (dimids) {
155
- for (const dimid of dimids) {
156
- const dim = getDim(module, ncid, dimid);
280
+ for (let i = 0; i < dimids.length; i++) {
281
+ const dimid = dimids[i];
282
+ const dim = getDim(module, workingNcid, dimid);
157
283
  size *= dim.len;
158
284
  dims.push(dim);
159
285
  shape.push(dim.len);
286
+ dimensions.push(dim.name);
160
287
  }
161
288
  }
162
- //Attribute Info
289
+ // Attribute Info
163
290
  const attNames = [];
164
291
  if (result.natts) {
165
292
  for (let i = 0; i < result.natts; i++) {
166
- const attname = getAttributeName(module, ncid, varid, i);
167
- attNames.push(attname);
293
+ attNames.push(getAttributeName(module, workingNcid, varid, i));
168
294
  }
169
295
  }
170
296
  const atts = {};
171
- if (attNames.length > 0) {
172
- for (const attname of attNames) {
173
- if (!attname)
174
- continue;
175
- atts[attname] = getAttributeValues(module, ncid, varid, attname);
176
- }
297
+ for (const attname of attNames) {
298
+ if (!attname)
299
+ continue;
300
+ atts[attname] = getAttributeValues(module, workingNcid, varid, attname);
177
301
  }
178
- //Chunking Info
179
- let chunks;
180
- const chunkResult = module.nc_inq_var_chunking(ncid, varid);
302
+ // Chunking Info
303
+ const chunkResult = module.nc_inq_var_chunking(workingNcid, varid);
181
304
  const isChunked = chunkResult.chunking === NC_CONSTANTS.NC_CHUNKED;
182
- if (isChunked) {
183
- chunks = chunkResult.chunkSizes;
184
- }
185
- else {
186
- chunks = shape;
187
- }
305
+ const chunks = isChunked ? chunkResult.chunkSizes : shape;
188
306
  const chunkElements = chunks.reduce((a, b) => a * b, 1);
189
- //Output
307
+ // Output
190
308
  info["name"] = result.name;
191
- info["dtype"] = CONSTANT_DTYPE_MAP[result.type];
192
- info['nctype'] = result.type;
309
+ info["dtype"] = enumCtx.isEnum ? `enum(${CONSTANT_DTYPE_MAP[actualType]})` : CONSTANT_DTYPE_MAP[actualType];
310
+ info["dtype_base"] = CONSTANT_DTYPE_MAP[actualType];
311
+ info["nctype"] = varType;
312
+ info["nctype_base"] = actualType;
193
313
  info["shape"] = shape;
194
- info['dims'] = dims;
314
+ info["dims"] = dims;
315
+ info["dimensions"] = dimensions;
195
316
  info["size"] = size;
196
317
  info["totalSize"] = size * typeMultiplier;
197
318
  info["attributes"] = atts;
198
319
  info["chunked"] = isChunked;
199
320
  info["chunks"] = chunks;
200
321
  info["chunkSize"] = chunkElements * typeMultiplier;
322
+ if (enumCtx.isEnum) {
323
+ info["enum"] = enumCtx.enumDict;
324
+ info["enumType"] = { name: result.name, baseType: actualType };
325
+ }
201
326
  return info;
202
327
  }
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);
328
+ // Helper function to convert enum values to names
329
+ function convertEnumValuesToNames(data, enumDict) {
330
+ const enumNames = [];
331
+ for (let i = 0; i < data.length; i++) {
332
+ const value = data[i];
333
+ const numValue = typeof value === 'bigint' ? Number(value) : Number(value);
334
+ enumNames.push(enumDict[numValue] ?? `Unknown(${numValue})`);
335
+ }
336
+ return enumNames;
337
+ }
338
+ export function getVariableArray(module, ncid, variable, groupPath, options) {
339
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
340
+ let varid;
341
+ if (typeof variable === "number") {
342
+ varid = variable;
343
+ }
344
+ else {
345
+ const result = module.nc_inq_varid(workingNcid, variable);
346
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
347
+ throw new Error(`Failed to get variable id for '${variable}' (error: ${result.result})`);
348
+ }
208
349
  varid = result.varid;
209
350
  }
210
- const info = getVariableInfo(module, ncid, varid);
211
- const arraySize = info.size;
212
- const arrayType = info.nctype;
213
- if (!arrayType || !arraySize)
214
- throw new Error("Failed to allocate memory for array");
351
+ const { size: arraySize, enumCtx } = resolveVariableType(module, workingNcid, varid);
352
+ const arrayType = enumCtx.baseType;
353
+ const readers = {
354
+ [NC_CONSTANTS.NC_CHAR]: (...args) => module.nc_get_var_text(...args),
355
+ [NC_CONSTANTS.NC_BYTE]: (...args) => module.nc_get_var_schar(...args),
356
+ [NC_CONSTANTS.NC_UBYTE]: (...args) => module.nc_get_var_uchar(...args),
357
+ [NC_CONSTANTS.NC_SHORT]: (...args) => module.nc_get_var_short(...args),
358
+ [NC_CONSTANTS.NC_USHORT]: (...args) => module.nc_get_var_ushort(...args),
359
+ [NC_CONSTANTS.NC_INT]: (...args) => module.nc_get_var_int(...args),
360
+ [NC_CONSTANTS.NC_UINT]: (...args) => module.nc_get_var_uint(...args),
361
+ [NC_CONSTANTS.NC_FLOAT]: (...args) => module.nc_get_var_float(...args),
362
+ [NC_CONSTANTS.NC_DOUBLE]: (...args) => module.nc_get_var_double(...args),
363
+ [NC_CONSTANTS.NC_INT64]: (...args) => module.nc_get_var_longlong(...args),
364
+ [NC_CONSTANTS.NC_LONGLONG]: (...args) => module.nc_get_var_longlong(...args),
365
+ [NC_CONSTANTS.NC_UINT64]: (...args) => module.nc_get_var_ulonglong(...args),
366
+ [NC_CONSTANTS.NC_ULONGLONG]: (...args) => module.nc_get_var_ulonglong(...args),
367
+ [NC_CONSTANTS.NC_STRING]: (...args) => module.nc_get_var_string(...args),
368
+ };
215
369
  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");
370
+ if (enumCtx.isEnum) {
371
+ arrayData = module.nc_get_var_generic(workingNcid, varid, arraySize, arrayType);
372
+ }
373
+ else {
374
+ const reader = readers[arrayType];
375
+ if (!reader) {
376
+ console.warn(`Unknown NetCDF type ${arrayType}, falling back to double`);
377
+ arrayData = module.nc_get_var_double(workingNcid, varid, arraySize);
378
+ }
379
+ else {
380
+ arrayData = reader(workingNcid, varid, arraySize);
381
+ }
382
+ }
383
+ if (arrayData.result !== NC_CONSTANTS.NC_NOERR) {
384
+ throw new Error(`Failed to read array data (error: ${arrayData.result})`);
385
+ }
386
+ if (!arrayData.data) {
387
+ throw new Error("nc_get_var returned no data");
388
+ }
389
+ if (enumCtx.isEnum && options?.convertEnumsToNames && enumCtx.enumDict) {
390
+ return convertEnumValuesToNames(arrayData.data, enumCtx.enumDict);
391
+ }
232
392
  return arrayData.data;
233
393
  }
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);
394
+ export function getSlicedVariableArray(module, ncid, variable, start, count, groupPath, options) {
395
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
396
+ let varid;
397
+ if (typeof variable === "number") {
398
+ varid = variable;
399
+ }
400
+ else {
401
+ const result = module.nc_inq_varid(workingNcid, variable);
402
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
403
+ throw new Error(`Failed to get variable id for '${variable}' (error: ${result.result})`);
404
+ }
239
405
  varid = result.varid;
240
406
  }
241
- const info = getVariableInfo(module, ncid, varid);
242
- const arrayType = info.nctype;
243
- if (!arrayType)
244
- throw new Error("Failed to allocate memory for array");
407
+ const { enumCtx } = resolveVariableType(module, workingNcid, varid);
408
+ const arrayType = enumCtx.baseType;
409
+ const readers = {
410
+ [NC_CONSTANTS.NC_BYTE]: (...args) => module.nc_get_vara_schar(...args),
411
+ [NC_CONSTANTS.NC_UBYTE]: (...args) => module.nc_get_vara_uchar(...args),
412
+ [NC_CONSTANTS.NC_SHORT]: (...args) => module.nc_get_vara_short(...args),
413
+ [NC_CONSTANTS.NC_USHORT]: (...args) => module.nc_get_vara_ushort(...args),
414
+ [NC_CONSTANTS.NC_INT]: (...args) => module.nc_get_vara_int(...args),
415
+ [NC_CONSTANTS.NC_UINT]: (...args) => module.nc_get_vara_uint(...args),
416
+ [NC_CONSTANTS.NC_FLOAT]: (...args) => module.nc_get_vara_float(...args),
417
+ [NC_CONSTANTS.NC_DOUBLE]: (...args) => module.nc_get_vara_double(...args),
418
+ [NC_CONSTANTS.NC_INT64]: (...args) => module.nc_get_vara_longlong(...args),
419
+ [NC_CONSTANTS.NC_UINT64]: (...args) => module.nc_get_vara_ulonglong(...args),
420
+ [NC_CONSTANTS.NC_STRING]: (...args) => module.nc_get_vara_string(...args),
421
+ };
245
422
  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);
423
+ if (enumCtx.isEnum) {
424
+ arrayData = module.nc_get_vara_generic(workingNcid, varid, start, count, arrayType);
425
+ }
426
+ else {
427
+ const reader = readers[arrayType];
428
+ if (!reader) {
429
+ console.warn(`Unknown NetCDF type ${arrayType}, falling back to double`);
430
+ arrayData = module.nc_get_vara_double(workingNcid, varid, start, count);
431
+ }
432
+ else {
433
+ arrayData = reader(workingNcid, varid, start, count);
434
+ }
435
+ }
436
+ if (arrayData.result !== NC_CONSTANTS.NC_NOERR) {
437
+ throw new Error(`Failed to read sliced array data (error: ${arrayData.result})`);
438
+ }
256
439
  if (!arrayData.data) {
257
- console.log(arrayData);
258
- throw new Error("Failed to read array data");
440
+ throw new Error("nc_get_vara returned no data");
441
+ }
442
+ if (enumCtx.isEnum && options?.convertEnumsToNames && enumCtx.enumDict) {
443
+ return convertEnumValuesToNames(arrayData.data, enumCtx.enumDict);
259
444
  }
260
445
  return arrayData.data;
261
446
  }
262
- // if (!module) throw new Error("Failed to load module. Ensure module is initialized before calling methods")
447
+ //---- Group Functions ----//
448
+ /**
449
+ * Get group ncid by path (supports nested groups)
450
+ * Uses nc_inq_grp_full_ncid for absolute paths and manual traversal for relative paths
451
+ * @param module - NetCDF4 module
452
+ * @param ncid - Current ncid (can be root or any group)
453
+ * @param groupPath - Can be absolute ("/group1/subgroup") or relative ("subgroup" or "group1/subgroup")
454
+ * @returns The ncid of the requested group
455
+ */
456
+ export function getGroupNCID(module, ncid, groupPath) {
457
+ // Optimization: if path is root, return the ncid
458
+ if (groupPath === '/' || groupPath === '') {
459
+ return ncid;
460
+ }
461
+ // Try using nc_inq_grp_full_ncid for absolute paths (more efficient)
462
+ if (groupPath.startsWith('/')) {
463
+ const result = module.nc_inq_grp_full_ncid(ncid, groupPath);
464
+ if (result.result === NC_CONSTANTS.NC_NOERR) {
465
+ return result.grp_ncid;
466
+ }
467
+ // Get current path for better error message
468
+ const currentPath = getGroupPath(module, ncid);
469
+ throw new Error(`Failed to find group '${groupPath}' from '${currentPath}' (error: ${result.result})`);
470
+ }
471
+ // Manual traversal for relative paths
472
+ const parts = groupPath.split('/').filter(p => p.length > 0);
473
+ let currentNcid = ncid;
474
+ for (const part of parts) {
475
+ const result = module.nc_inq_grp_ncid(currentNcid, part);
476
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
477
+ const currentPath = getGroupPath(module, currentNcid);
478
+ throw new Error(`Failed to get group ncid for '${part}' in path '${groupPath}' from '${currentPath}' (error: ${result.result})`);
479
+ }
480
+ currentNcid = result.grp_ncid;
481
+ }
482
+ return currentNcid;
483
+ }
484
+ /**
485
+ * Alias for getGroupNCID (matches nc_inq_ncid API)
486
+ * @param module - NetCDF4 module
487
+ * @param ncid - Current ncid
488
+ * @param groupName - Group name or path
489
+ * @returns The ncid of the requested group
490
+ */
491
+ export const getNCID = getGroupNCID;
492
+ /**
493
+ * Get immediate child groups (non-recursive)
494
+ * @param module - NetCDF4 module
495
+ * @param ncid - Group ncid to query
496
+ * @returns Object mapping group names to their ncids
497
+ */
498
+ export function getGroups(module, ncid) {
499
+ const result = module.nc_inq_grps(ncid);
500
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
501
+ throw new Error(`Failed to get groups (error: ${result.result})`);
502
+ }
503
+ const groups = {};
504
+ const grpids = result.grpids || [];
505
+ for (const grpid of grpids) {
506
+ const nameResult = module.nc_inq_grpname(grpid);
507
+ if (nameResult.result === NC_CONSTANTS.NC_NOERR && nameResult.name) {
508
+ groups[nameResult.name] = grpid;
509
+ }
510
+ }
511
+ return groups;
512
+ }
513
+ /**
514
+ * Get all groups recursively (returns nested structure)
515
+ * @param module - NetCDF4 module
516
+ * @param ncid - Group ncid to start from (usually root)
517
+ * @returns Nested object structure with group names, ncids, and subgroups
518
+ */
519
+ export function getGroupsRecursive(module, ncid) {
520
+ const result = module.nc_inq_grps(ncid);
521
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
522
+ throw new Error(`Failed to get groups (error: ${result.result})`);
523
+ }
524
+ const groups = {};
525
+ const grpids = result.grpids || [];
526
+ for (const grpid of grpids) {
527
+ const nameResult = module.nc_inq_grpname(grpid);
528
+ if (nameResult.result === NC_CONSTANTS.NC_NOERR && nameResult.name) {
529
+ groups[nameResult.name] = {
530
+ ncid: grpid,
531
+ subgroups: getGroupsRecursive(module, grpid) // Recursive call
532
+ };
533
+ }
534
+ }
535
+ return groups;
536
+ }
537
+ /**
538
+ * Get the name of a group
539
+ * @param module - NetCDF4 module
540
+ * @param ncid - Group ncid
541
+ * @returns Group name
542
+ */
543
+ export function getGroupName(module, ncid) {
544
+ const result = module.nc_inq_grpname(ncid);
545
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
546
+ throw new Error(`Failed to get group name (error: ${result.result})`);
547
+ }
548
+ return result.name || '';
549
+ }
550
+ /**
551
+ * Get the full absolute path of a group
552
+ * Uses nc_inq_grpname_full for efficient path retrieval
553
+ * @param module - NetCDF4 module
554
+ * @param ncid - Group ncid
555
+ * @returns Full path like "/group1/subgroup"
556
+ */
557
+ export function getGroupPath(module, ncid) {
558
+ // Use nc_inq_grpname_full to get the complete path efficiently
559
+ const result = module.nc_inq_grpname_full(ncid);
560
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
561
+ throw new Error(`Failed to get group full name (error: ${result.result})`);
562
+ }
563
+ return result.full_name || '/';
564
+ }
565
+ /**
566
+ * Get the length of a group's full path name
567
+ * @param module - NetCDF4 module
568
+ * @param ncid - Group ncid
569
+ * @returns Length of the full group path name
570
+ */
571
+ export function getGroupPathLength(module, ncid) {
572
+ const result = module.nc_inq_grpname_len(ncid);
573
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
574
+ throw new Error(`Failed to get group name length (error: ${result.result})`);
575
+ }
576
+ return result.lenp || 0;
577
+ }
578
+ /**
579
+ * Get the parent group ncid
580
+ * @param module - NetCDF4 module
581
+ * @param ncid - Group ncid
582
+ * @returns Parent group ncid, or null if this is the root group
583
+ */
584
+ export function getGroupParent(module, ncid) {
585
+ const result = module.nc_inq_grp_parent(ncid);
586
+ if (result.result !== NC_CONSTANTS.NC_NOERR) {
587
+ return null; // No parent (at root)
588
+ }
589
+ return result.parent_ncid;
590
+ }
591
+ function findDimInHierarchy(module, startNcid, name) {
592
+ let current = startNcid;
593
+ while (current !== null) {
594
+ const result = module.nc_inq_dimid(current, name);
595
+ if (result.result === NC_CONSTANTS.NC_NOERR) {
596
+ return true;
597
+ }
598
+ current = getGroupParent(module, current);
599
+ }
600
+ return false;
601
+ }
602
+ /**
603
+ * Get complete hierarchy: groups + their variables, dimensions, and attributes recursively
604
+ * This is the unified method that should be used for exploring the file structure
605
+ * @param module - NetCDF4 module
606
+ * @param ncid - Group ncid to start from
607
+ * @param groupPath - Optional path to a specific starting group
608
+ * @param includeParentDims - Whether to include parent dimensions in each group (default: false)
609
+ * @returns Complete hierarchical structure
610
+ */
611
+ export function getCompleteHierarchy(module, ncid, groupPath, includeParentDims = false) {
612
+ const workingNcid = groupPath ? getGroupNCID(module, ncid, groupPath) : ncid;
613
+ // Get variables at this level (now includes ncid)
614
+ const variables = getGroupVariables(module, workingNcid);
615
+ // Get dimensions at this level
616
+ const dimensions = getDims(module, workingNcid);
617
+ // If includeParentDims is true, also get parent dimensions
618
+ if (includeParentDims) {
619
+ const result = module.nc_inq_dimids(workingNcid, 1);
620
+ if (result.result === NC_CONSTANTS.NC_NOERR && result.dimids) {
621
+ const parentDims = {};
622
+ for (const dimid of result.dimids) {
623
+ const dim = getDim(module, workingNcid, dimid);
624
+ // Only add if not already in dimensions (avoid duplicates)
625
+ if (!dimensions[dim.name]) {
626
+ parentDims[dim.name] = {
627
+ size: dim.len,
628
+ units: dim.units ?? null,
629
+ id: dim.id,
630
+ inherited: true
631
+ };
632
+ }
633
+ }
634
+ // Merge parent dimensions
635
+ Object.assign(dimensions, parentDims);
636
+ }
637
+ }
638
+ // Get attributes at this level
639
+ const attributes = getGlobalAttributes(module, workingNcid);
640
+ // Get the full path for this group
641
+ const fullPath = getGroupPath(module, workingNcid);
642
+ // Get subgroups and recurse
643
+ const groupsResult = module.nc_inq_grps(workingNcid);
644
+ const subgroups = {};
645
+ if (groupsResult.result === NC_CONSTANTS.NC_NOERR && groupsResult.grpids) {
646
+ for (const grpid of groupsResult.grpids) {
647
+ const nameResult = module.nc_inq_grpname(grpid);
648
+ if (nameResult.result === NC_CONSTANTS.NC_NOERR && nameResult.name) {
649
+ // Recursively get the complete hierarchy for this subgroup
650
+ subgroups[nameResult.name] = getCompleteHierarchy(module, grpid, undefined, includeParentDims);
651
+ }
652
+ }
653
+ }
654
+ return {
655
+ ncid: workingNcid, // Include the ncid at this level
656
+ path: fullPath, // Include the full path
657
+ variables,
658
+ dimensions,
659
+ attributes,
660
+ groups: subgroups
661
+ };
662
+ }
663
+ /**
664
+ * Get all variables recursively from all groups
665
+ * Returns a flat structure with full paths as keys
666
+ * @param module - NetCDF4 module
667
+ * @param ncid - Group ncid to start from
668
+ * @param currentPath - Current path (used internally for recursion)
669
+ * @returns Flat dictionary with full variable paths
670
+ */
671
+ export function getVariables(module, ncid, currentPath) {
672
+ const allVars = {};
673
+ // Get the actual path if not provided
674
+ const path = currentPath || getGroupPath(module, ncid);
675
+ // Get variables at current level
676
+ const vars = getGroupVariables(module, ncid);
677
+ for (const [name, varData] of Object.entries(vars)) {
678
+ const fullPath = path === '/' ? `/${name}` : `${path}/${name}`;
679
+ allVars[fullPath] = { ...varData, path, ncid };
680
+ }
681
+ // Recurse into subgroups
682
+ const groupsResult = module.nc_inq_grps(ncid);
683
+ if (groupsResult.result === NC_CONSTANTS.NC_NOERR && groupsResult.grpids) {
684
+ for (const grpid of groupsResult.grpids) {
685
+ const nameResult = module.nc_inq_grpname(grpid);
686
+ if (nameResult.result === NC_CONSTANTS.NC_NOERR && nameResult.name) {
687
+ const newPath = path === '/' ? `/${nameResult.name}` : `${path}/${nameResult.name}`;
688
+ const subVars = getVariables(module, grpid, newPath);
689
+ Object.assign(allVars, subVars);
690
+ }
691
+ }
692
+ }
693
+ return allVars;
694
+ }
263
695
  //# sourceMappingURL=netcdf-getters.js.map