@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.
- package/CONTRIBUTING.md +151 -0
- package/LICENSE +1 -0
- package/README.md +65 -134
- package/dist/constants.d.ts +7 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +26 -3
- package/dist/constants.js.map +1 -1
- package/dist/group.d.ts +3 -1
- package/dist/group.d.ts.map +1 -1
- package/dist/group.js +45 -4
- package/dist/group.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/netcdf-getters.d.ts +87 -8
- package/dist/netcdf-getters.d.ts.map +1 -1
- package/dist/netcdf-getters.js +441 -104
- package/dist/netcdf-getters.js.map +1 -1
- package/dist/netcdf-worker.js +63 -68
- package/dist/netcdf-worker.js.map +1 -1
- package/dist/netcdf4-wasm.js +1 -1
- package/dist/netcdf4-wasm.wasm +0 -0
- package/dist/netcdf4.d.ts +139 -12
- package/dist/netcdf4.d.ts.map +1 -1
- package/dist/netcdf4.js +422 -36
- package/dist/netcdf4.js.map +1 -1
- package/dist/types.d.ts +106 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +1 -1
- package/dist/wasm-module.d.ts.map +1 -1
- package/dist/wasm-module.js +337 -5
- package/dist/wasm-module.js.map +1 -1
- package/package.json +19 -4
package/dist/netcdf4.js
CHANGED
|
@@ -151,6 +151,9 @@ export class NetCDF4 extends Group {
|
|
|
151
151
|
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
|
152
152
|
this.loadMockDimensions();
|
|
153
153
|
}
|
|
154
|
+
else {
|
|
155
|
+
await this.load();
|
|
156
|
+
}
|
|
154
157
|
}
|
|
155
158
|
this._isOpen = true;
|
|
156
159
|
}
|
|
@@ -263,22 +266,22 @@ export class NetCDF4 extends Group {
|
|
|
263
266
|
});
|
|
264
267
|
});
|
|
265
268
|
}
|
|
266
|
-
async getGlobalAttributes() {
|
|
269
|
+
async getGlobalAttributes(groupPath) {
|
|
267
270
|
if (this.worker) {
|
|
268
|
-
return this.callWorker('getGlobalAttributes');
|
|
271
|
+
return this.callWorker('getGlobalAttributes', { groupPath });
|
|
269
272
|
}
|
|
270
273
|
else {
|
|
271
274
|
// Main thread path is already synchronous (or could be wrapped in Promise.resolve)
|
|
272
|
-
return NCGet.getGlobalAttributes(this.module, this.ncid);
|
|
275
|
+
return NCGet.getGlobalAttributes(this.module, this.ncid, groupPath);
|
|
273
276
|
}
|
|
274
277
|
}
|
|
275
|
-
async getFullMetadata() {
|
|
278
|
+
async getFullMetadata(groupPath) {
|
|
276
279
|
if (this.worker) {
|
|
277
|
-
return this.callWorker('getFullMetadata');
|
|
280
|
+
return this.callWorker('getFullMetadata', { groupPath });
|
|
278
281
|
}
|
|
279
282
|
else {
|
|
280
283
|
// Main thread path is already synchronous (or could be wrapped in Promise.resolve)
|
|
281
|
-
return NCGet.getFullMetadata(this.module, this.ncid);
|
|
284
|
+
return NCGet.getFullMetadata(this.module, this.ncid, groupPath);
|
|
282
285
|
}
|
|
283
286
|
}
|
|
284
287
|
async getAttributeValues(varid, attname) {
|
|
@@ -290,67 +293,67 @@ export class NetCDF4 extends Group {
|
|
|
290
293
|
return NCGet.getAttributeValues(this.module, this.ncid, varid, attname);
|
|
291
294
|
}
|
|
292
295
|
}
|
|
293
|
-
async getDimCount() {
|
|
296
|
+
async getDimCount(ncid = this.ncid) {
|
|
294
297
|
if (this.worker) {
|
|
295
|
-
return this.callWorker('getDimCount');
|
|
298
|
+
return this.callWorker('getDimCount', { ncid });
|
|
296
299
|
}
|
|
297
300
|
else {
|
|
298
301
|
// Main thread path is already synchronous (or could be wrapped in Promise.resolve)
|
|
299
|
-
return NCGet.getDimCount(this.module,
|
|
302
|
+
return NCGet.getDimCount(this.module, ncid);
|
|
300
303
|
}
|
|
301
304
|
}
|
|
302
|
-
async
|
|
305
|
+
async getGroupVariables(groupPath) {
|
|
303
306
|
if (this.worker) {
|
|
304
|
-
return this.callWorker('
|
|
307
|
+
return this.callWorker('getGroupVariables', { groupPath });
|
|
305
308
|
}
|
|
306
309
|
else {
|
|
307
310
|
// Main thread path is already synchronous (or could be wrapped in Promise.resolve)
|
|
308
|
-
return NCGet.
|
|
311
|
+
return NCGet.getGroupVariables(this.module, this.ncid, groupPath);
|
|
309
312
|
}
|
|
310
313
|
}
|
|
311
|
-
async getVarIDs() {
|
|
314
|
+
async getVarIDs(ncid = this.ncid) {
|
|
312
315
|
if (this.worker) {
|
|
313
|
-
return this.callWorker('getVarIDs');
|
|
316
|
+
return this.callWorker('getVarIDs', { ncid });
|
|
314
317
|
}
|
|
315
318
|
else {
|
|
316
319
|
// Main thread path is already synchronous (or could be wrapped in Promise.resolve)
|
|
317
|
-
return NCGet.getVarIDs(this.module,
|
|
320
|
+
return NCGet.getVarIDs(this.module, ncid);
|
|
318
321
|
}
|
|
319
322
|
}
|
|
320
|
-
async getDimIDs() {
|
|
323
|
+
async getDimIDs(ncid = this.ncid) {
|
|
321
324
|
if (this.worker) {
|
|
322
|
-
return this.callWorker('getDimIDs');
|
|
325
|
+
return this.callWorker('getDimIDs', { ncid });
|
|
323
326
|
}
|
|
324
327
|
else {
|
|
325
328
|
// Main thread path is already synchronous (or could be wrapped in Promise.resolve)
|
|
326
|
-
return NCGet.getDimIDs(this.module,
|
|
329
|
+
return NCGet.getDimIDs(this.module, ncid);
|
|
327
330
|
}
|
|
328
331
|
}
|
|
329
|
-
async getDim(dimid) {
|
|
332
|
+
async getDim(dimid, ncid = this.ncid) {
|
|
330
333
|
if (this.worker) {
|
|
331
|
-
return this.callWorker('getDim', { dimid });
|
|
334
|
+
return this.callWorker('getDim', { dimid, ncid });
|
|
332
335
|
}
|
|
333
336
|
else {
|
|
334
337
|
// Main thread path is already synchronous (or could be wrapped in Promise.resolve)
|
|
335
|
-
return NCGet.getDim(this.module,
|
|
338
|
+
return NCGet.getDim(this.module, ncid, dimid);
|
|
336
339
|
}
|
|
337
340
|
}
|
|
338
|
-
async getDims() {
|
|
341
|
+
async getDims(groupPath) {
|
|
339
342
|
if (this.worker) {
|
|
340
|
-
return this.callWorker('getDims');
|
|
343
|
+
return this.callWorker('getDims', { groupPath });
|
|
341
344
|
}
|
|
342
345
|
else {
|
|
343
346
|
// Main thread path is already synchronous (or could be wrapped in Promise.resolve)
|
|
344
|
-
return NCGet.getDims(this.module, this.ncid);
|
|
347
|
+
return NCGet.getDims(this.module, this.ncid, groupPath);
|
|
345
348
|
}
|
|
346
349
|
}
|
|
347
|
-
async getVarCount() {
|
|
350
|
+
async getVarCount(ncid = this.ncid) {
|
|
348
351
|
if (this.worker) {
|
|
349
|
-
return this.callWorker('getVarCount');
|
|
352
|
+
return this.callWorker('getVarCount', { ncid });
|
|
350
353
|
}
|
|
351
354
|
else {
|
|
352
355
|
// Main thread path is already synchronous (or could be wrapped in Promise.resolve)
|
|
353
|
-
return NCGet.getVarCount(this.module,
|
|
356
|
+
return NCGet.getVarCount(this.module, ncid);
|
|
354
357
|
}
|
|
355
358
|
}
|
|
356
359
|
async getAttributeName(varid, attId) {
|
|
@@ -362,31 +365,72 @@ export class NetCDF4 extends Group {
|
|
|
362
365
|
return NCGet.getAttributeName(this.module, this.ncid, varid, attId);
|
|
363
366
|
}
|
|
364
367
|
}
|
|
365
|
-
async getVariableInfo(variable) {
|
|
368
|
+
async getVariableInfo(variable, groupPath) {
|
|
366
369
|
if (this.worker) {
|
|
367
|
-
return this.callWorker('getVariableInfo', { variable });
|
|
370
|
+
return this.callWorker('getVariableInfo', { variable, groupPath });
|
|
368
371
|
}
|
|
369
372
|
else {
|
|
370
373
|
// Main thread path is already synchronous (or could be wrapped in Promise.resolve)
|
|
371
|
-
return NCGet.getVariableInfo(this.module, this.ncid, variable);
|
|
374
|
+
return NCGet.getVariableInfo(this.module, this.ncid, variable, groupPath);
|
|
372
375
|
}
|
|
373
376
|
}
|
|
374
|
-
async getVariableArray(variable) {
|
|
377
|
+
async getVariableArray(variable, groupPath) {
|
|
375
378
|
if (this.worker) {
|
|
376
|
-
return this.callWorker('getVariableArray', { variable });
|
|
379
|
+
return this.callWorker('getVariableArray', { variable, groupPath });
|
|
377
380
|
}
|
|
378
381
|
else {
|
|
379
382
|
// Main thread path is already synchronous (or could be wrapped in Promise.resolve)
|
|
380
|
-
return NCGet.getVariableArray(this.module, this.ncid, variable);
|
|
383
|
+
return NCGet.getVariableArray(this.module, this.ncid, variable, groupPath);
|
|
381
384
|
}
|
|
382
385
|
}
|
|
383
|
-
async getSlicedVariableArray(variable, start, count) {
|
|
386
|
+
async getSlicedVariableArray(variable, start, count, groupPath) {
|
|
384
387
|
if (this.worker) {
|
|
385
|
-
return this.callWorker('getSlicedVariableArray', { variable, start, count });
|
|
388
|
+
return this.callWorker('getSlicedVariableArray', { variable, start, count, groupPath });
|
|
386
389
|
}
|
|
387
390
|
else {
|
|
388
391
|
// Main thread path is already synchronous (or could be wrapped in Promise.resolve)
|
|
389
|
-
return NCGet.getSlicedVariableArray(this.module, this.ncid, variable, start, count);
|
|
392
|
+
return NCGet.getSlicedVariableArray(this.module, this.ncid, variable, start, count, groupPath);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Group functions
|
|
396
|
+
async getGroups(ncid = this.ncid) {
|
|
397
|
+
if (this.worker) {
|
|
398
|
+
return this.callWorker('getGroups', { ncid });
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
return NCGet.getGroups(this.module, ncid);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
async getGroupsRecursive(ncid = this.ncid) {
|
|
405
|
+
if (this.worker) {
|
|
406
|
+
return this.callWorker('getGroupsRecursive', { ncid });
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
return NCGet.getGroupsRecursive(this.module, ncid);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async getGroupNCID(groupPath) {
|
|
413
|
+
if (this.worker) {
|
|
414
|
+
return this.callWorker('getGroupNCID', { groupPath });
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
return NCGet.getGroupNCID(this.module, this.ncid, groupPath);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async getGroupName(ncid = this.ncid) {
|
|
421
|
+
if (this.worker) {
|
|
422
|
+
return this.callWorker('getGroupName', { ncid });
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
return NCGet.getGroupName(this.module, ncid);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
async getGroupPath(ncid = this.ncid) {
|
|
429
|
+
if (this.worker) {
|
|
430
|
+
return this.callWorker('getGroupPath', { ncid });
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
return NCGet.getGroupPath(this.module, ncid);
|
|
390
434
|
}
|
|
391
435
|
}
|
|
392
436
|
async defineDimension(ncid, name, size) {
|
|
@@ -633,5 +677,347 @@ export class NetCDF4 extends Group {
|
|
|
633
677
|
const source = this.memorySource ? '(in-memory)' : '';
|
|
634
678
|
return `<netCDF4.Dataset '${this.filename}'${source}: mode = '${this.mode}', file format = '${this.file_format}', ${status}>`;
|
|
635
679
|
}
|
|
680
|
+
/**
|
|
681
|
+
* Get complete hierarchy of groups, variables, dimensions, and attributes
|
|
682
|
+
* This is the unified method for exploring the entire file structure
|
|
683
|
+
* @param groupPath - Optional path to start from a specific group
|
|
684
|
+
*/
|
|
685
|
+
async getCompleteHierarchy(groupPath) {
|
|
686
|
+
if (this.worker) {
|
|
687
|
+
return this.callWorker('getCompleteHierarchy', { groupPath });
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
return NCGet.getCompleteHierarchy(this.module, this.ncid, groupPath);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Get all variables recursively from all groups
|
|
695
|
+
* Returns a flat dictionary with full path keys like "/group1/var1"
|
|
696
|
+
*/
|
|
697
|
+
async getVariables() {
|
|
698
|
+
if (this.worker) {
|
|
699
|
+
return this.callWorker('getVariables');
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
return NCGet.getVariables(this.module, this.ncid);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* UI-friendly wrapper around NetCDF4
|
|
708
|
+
* Builds a full dataTree of groups, variables, attributes with enhanced navigation
|
|
709
|
+
*/
|
|
710
|
+
export class DataTree {
|
|
711
|
+
dataset;
|
|
712
|
+
tree = {};
|
|
713
|
+
groupTreeCache = null;
|
|
714
|
+
constructor(dataset) {
|
|
715
|
+
this.dataset = dataset;
|
|
716
|
+
}
|
|
717
|
+
async buildTree() {
|
|
718
|
+
this.tree = await this.dataset.getCompleteHierarchy();
|
|
719
|
+
// Clear cache when tree is rebuilt
|
|
720
|
+
this.groupTreeCache = null;
|
|
721
|
+
}
|
|
722
|
+
// --------------------------------------------------
|
|
723
|
+
// Core navigation
|
|
724
|
+
// --------------------------------------------------
|
|
725
|
+
getGroup(groupPath = '/') {
|
|
726
|
+
if (!this.tree)
|
|
727
|
+
return null;
|
|
728
|
+
if (groupPath === '/' || !groupPath)
|
|
729
|
+
return this.tree;
|
|
730
|
+
const parts = groupPath.split('/').filter(Boolean);
|
|
731
|
+
let current = this.tree;
|
|
732
|
+
for (const part of parts) {
|
|
733
|
+
if (!current.groups || !current.groups[part])
|
|
734
|
+
return null;
|
|
735
|
+
current = current.groups[part];
|
|
736
|
+
}
|
|
737
|
+
return current;
|
|
738
|
+
}
|
|
739
|
+
getGroupName(groupPath) {
|
|
740
|
+
if (!groupPath || groupPath === '/')
|
|
741
|
+
return 'root';
|
|
742
|
+
const parts = groupPath.split('/').filter(Boolean);
|
|
743
|
+
return parts[parts.length - 1];
|
|
744
|
+
}
|
|
745
|
+
hasSubgroups(groupPath = '/') {
|
|
746
|
+
const group = this.getGroup(groupPath);
|
|
747
|
+
return group ? Object.keys(group.groups || {}).length > 0 : false;
|
|
748
|
+
}
|
|
749
|
+
// --------------------------------------------------
|
|
750
|
+
// Groups (for dropdowns)
|
|
751
|
+
// --------------------------------------------------
|
|
752
|
+
/** immediate children only */
|
|
753
|
+
listGroups(groupPath = '/') {
|
|
754
|
+
const group = this.getGroup(groupPath);
|
|
755
|
+
if (!group || !group.groups)
|
|
756
|
+
return [];
|
|
757
|
+
return Object.entries(group.groups).map(([name, g]) => ({
|
|
758
|
+
name,
|
|
759
|
+
path: g.path || `${groupPath === '/' ? '' : groupPath}/${name}`
|
|
760
|
+
}));
|
|
761
|
+
}
|
|
762
|
+
/** every group recursively */
|
|
763
|
+
listAllGroups() {
|
|
764
|
+
const result = [];
|
|
765
|
+
const walk = (g) => {
|
|
766
|
+
if (!g.groups)
|
|
767
|
+
return;
|
|
768
|
+
for (const [name, sub] of Object.entries(g.groups)) {
|
|
769
|
+
result.push({ name, path: sub.path });
|
|
770
|
+
walk(sub);
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
walk(this.tree);
|
|
774
|
+
return result;
|
|
775
|
+
}
|
|
776
|
+
// --------------------------------------------------
|
|
777
|
+
// Hierarchical Group Tree
|
|
778
|
+
// --------------------------------------------------
|
|
779
|
+
/**
|
|
780
|
+
* Build a hierarchical tree structure of all groups
|
|
781
|
+
* Returns a tree with parent-child relationships
|
|
782
|
+
* Results are cached until buildTree() is called again
|
|
783
|
+
*/
|
|
784
|
+
buildGroupTree() {
|
|
785
|
+
// Return cached version if available
|
|
786
|
+
if (this.groupTreeCache) {
|
|
787
|
+
return this.groupTreeCache;
|
|
788
|
+
}
|
|
789
|
+
const allGroups = this.listAllGroups();
|
|
790
|
+
// Create root node
|
|
791
|
+
const root = {
|
|
792
|
+
name: '/',
|
|
793
|
+
path: '/',
|
|
794
|
+
children: [],
|
|
795
|
+
hasVariables: this.hasVariables('/'),
|
|
796
|
+
hasAttributes: this.hasAttributes('/'),
|
|
797
|
+
variableCount: this.getVariableCount('/'),
|
|
798
|
+
attributeCount: this.getAttributeCount('/')
|
|
799
|
+
};
|
|
800
|
+
// Map to store all nodes by path for quick lookup
|
|
801
|
+
const nodeMap = new Map();
|
|
802
|
+
nodeMap.set('/', root);
|
|
803
|
+
// Sort groups by path depth to ensure parents are created before children
|
|
804
|
+
const sortedGroups = allGroups.sort((a, b) => {
|
|
805
|
+
const depthA = a.path.split('/').filter(Boolean).length;
|
|
806
|
+
const depthB = b.path.split('/').filter(Boolean).length;
|
|
807
|
+
return depthA - depthB;
|
|
808
|
+
});
|
|
809
|
+
// Build the tree
|
|
810
|
+
for (const { name, path } of sortedGroups) {
|
|
811
|
+
const node = {
|
|
812
|
+
name,
|
|
813
|
+
path,
|
|
814
|
+
children: [],
|
|
815
|
+
hasVariables: this.hasVariables(path),
|
|
816
|
+
hasAttributes: this.hasAttributes(path),
|
|
817
|
+
variableCount: this.getVariableCount(path),
|
|
818
|
+
attributeCount: this.getAttributeCount(path)
|
|
819
|
+
};
|
|
820
|
+
nodeMap.set(path, node);
|
|
821
|
+
// Find parent path
|
|
822
|
+
const pathParts = path.split('/').filter(Boolean);
|
|
823
|
+
const parentPath = pathParts.length === 1
|
|
824
|
+
? '/'
|
|
825
|
+
: '/' + pathParts.slice(0, -1).join('/');
|
|
826
|
+
const parent = nodeMap.get(parentPath);
|
|
827
|
+
if (parent) {
|
|
828
|
+
parent.children.push(node);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
// Cache the result
|
|
832
|
+
this.groupTreeCache = root;
|
|
833
|
+
return root;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Get a specific node from the group tree by path
|
|
837
|
+
*/
|
|
838
|
+
getGroupNode(path) {
|
|
839
|
+
const tree = this.buildGroupTree();
|
|
840
|
+
if (path === '/')
|
|
841
|
+
return tree;
|
|
842
|
+
const parts = path.split('/').filter(Boolean);
|
|
843
|
+
let current = tree;
|
|
844
|
+
for (const part of parts) {
|
|
845
|
+
const child = current.children.find(c => c.name === part);
|
|
846
|
+
if (!child)
|
|
847
|
+
return null;
|
|
848
|
+
current = child;
|
|
849
|
+
}
|
|
850
|
+
return current;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Get breadcrumb trail for a given path
|
|
854
|
+
* Returns array of {name, path} from root to target
|
|
855
|
+
*/
|
|
856
|
+
getBreadcrumbs(groupPath) {
|
|
857
|
+
if (groupPath === '/') {
|
|
858
|
+
return [{ name: 'root', path: '/' }];
|
|
859
|
+
}
|
|
860
|
+
const parts = groupPath.split('/').filter(Boolean);
|
|
861
|
+
const breadcrumbs = [
|
|
862
|
+
{ name: 'root', path: '/' }
|
|
863
|
+
];
|
|
864
|
+
let currentPath = '';
|
|
865
|
+
for (const part of parts) {
|
|
866
|
+
currentPath += '/' + part;
|
|
867
|
+
breadcrumbs.push({
|
|
868
|
+
name: part,
|
|
869
|
+
path: currentPath
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
return breadcrumbs;
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Search for groups by name (case-insensitive)
|
|
876
|
+
*/
|
|
877
|
+
searchGroups(query) {
|
|
878
|
+
const lowerQuery = query.toLowerCase();
|
|
879
|
+
return this.listAllGroups().filter(g => g.name.toLowerCase().includes(lowerQuery) ||
|
|
880
|
+
g.path.toLowerCase().includes(lowerQuery));
|
|
881
|
+
}
|
|
882
|
+
// --------------------------------------------------
|
|
883
|
+
// Variables
|
|
884
|
+
// --------------------------------------------------
|
|
885
|
+
/** variables inside a group */
|
|
886
|
+
getAllVariables(groupPath = '/') {
|
|
887
|
+
const group = this.getGroup(groupPath);
|
|
888
|
+
return group?.variables || {};
|
|
889
|
+
}
|
|
890
|
+
/** check if group has variables */
|
|
891
|
+
hasVariables(groupPath = '/') {
|
|
892
|
+
const vars = this.getAllVariables(groupPath);
|
|
893
|
+
return Object.keys(vars).length > 0;
|
|
894
|
+
}
|
|
895
|
+
/** count variables in a group */
|
|
896
|
+
getVariableCount(groupPath = '/') {
|
|
897
|
+
return Object.keys(this.getAllVariables(groupPath)).length;
|
|
898
|
+
}
|
|
899
|
+
/** get variable names as array */
|
|
900
|
+
getVariableNames(groupPath = '/') {
|
|
901
|
+
return Object.keys(this.getAllVariables(groupPath));
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Search for variables by name across all groups
|
|
905
|
+
*/
|
|
906
|
+
searchVariables(query) {
|
|
907
|
+
const results = [];
|
|
908
|
+
const lowerQuery = query.toLowerCase();
|
|
909
|
+
const searchInGroup = (groupPath) => {
|
|
910
|
+
const vars = this.getAllVariables(groupPath);
|
|
911
|
+
for (const varName of Object.keys(vars)) {
|
|
912
|
+
if (varName.toLowerCase().includes(lowerQuery)) {
|
|
913
|
+
results.push({
|
|
914
|
+
name: varName,
|
|
915
|
+
groupPath,
|
|
916
|
+
path: `${groupPath === '/' ? '' : groupPath}/${varName}`
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
// Recurse into subgroups
|
|
921
|
+
const subgroups = this.listGroups(groupPath);
|
|
922
|
+
for (const { path } of subgroups) {
|
|
923
|
+
searchInGroup(path);
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
searchInGroup('/');
|
|
927
|
+
return results;
|
|
928
|
+
}
|
|
929
|
+
// --------------------------------------------------
|
|
930
|
+
// Attributes
|
|
931
|
+
// --------------------------------------------------
|
|
932
|
+
/** attributes from the tree (fast) */
|
|
933
|
+
getAttributes(groupPath = '/') {
|
|
934
|
+
const group = this.getGroup(groupPath);
|
|
935
|
+
return group?.attributes || {};
|
|
936
|
+
}
|
|
937
|
+
/** check if group has attributes */
|
|
938
|
+
hasAttributes(groupPath = '/') {
|
|
939
|
+
const attrs = this.getAttributes(groupPath);
|
|
940
|
+
return Object.keys(attrs).length > 0;
|
|
941
|
+
}
|
|
942
|
+
/** count attributes in a group */
|
|
943
|
+
getAttributeCount(groupPath = '/') {
|
|
944
|
+
return Object.keys(this.getAttributes(groupPath)).length;
|
|
945
|
+
}
|
|
946
|
+
// --------------------------------------------------
|
|
947
|
+
// Dimensions
|
|
948
|
+
// --------------------------------------------------
|
|
949
|
+
/** get dimensions for a group */
|
|
950
|
+
getDimensions(groupPath = '/') {
|
|
951
|
+
const group = this.getGroup(groupPath);
|
|
952
|
+
return group?.dimensions || {};
|
|
953
|
+
}
|
|
954
|
+
/** check if group has dimensions */
|
|
955
|
+
hasDimensions(groupPath = '/') {
|
|
956
|
+
const dims = this.getDimensions(groupPath);
|
|
957
|
+
return Object.keys(dims).length > 0;
|
|
958
|
+
}
|
|
959
|
+
/** count dimensions in a group */
|
|
960
|
+
getDimensionCount(groupPath = '/') {
|
|
961
|
+
return Object.keys(this.getDimensions(groupPath)).length;
|
|
962
|
+
}
|
|
963
|
+
// --------------------------------------------------
|
|
964
|
+
// Statistics and Summaries
|
|
965
|
+
// --------------------------------------------------
|
|
966
|
+
/**
|
|
967
|
+
* Get summary statistics for a group
|
|
968
|
+
*/
|
|
969
|
+
getGroupSummary(groupPath = '/') {
|
|
970
|
+
const group = this.getGroup(groupPath);
|
|
971
|
+
if (!group)
|
|
972
|
+
return null;
|
|
973
|
+
return {
|
|
974
|
+
path: groupPath,
|
|
975
|
+
name: this.getGroupName(groupPath),
|
|
976
|
+
variableCount: this.getVariableCount(groupPath),
|
|
977
|
+
attributeCount: this.getAttributeCount(groupPath),
|
|
978
|
+
dimensionCount: this.getDimensionCount(groupPath),
|
|
979
|
+
subgroupCount: this.listGroups(groupPath).length,
|
|
980
|
+
hasSubgroups: this.hasSubgroups(groupPath)
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Get complete statistics for the entire dataset
|
|
985
|
+
*/
|
|
986
|
+
getDatasetSummary() {
|
|
987
|
+
let totalVariables = 0;
|
|
988
|
+
let totalAttributes = 0;
|
|
989
|
+
let totalDimensions = 0;
|
|
990
|
+
let maxDepth = 0;
|
|
991
|
+
const countInGroup = (groupPath, depth) => {
|
|
992
|
+
totalVariables += this.getVariableCount(groupPath);
|
|
993
|
+
totalAttributes += this.getAttributeCount(groupPath);
|
|
994
|
+
totalDimensions += this.getDimensionCount(groupPath);
|
|
995
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
996
|
+
const subgroups = this.listGroups(groupPath);
|
|
997
|
+
for (const { path } of subgroups) {
|
|
998
|
+
countInGroup(path, depth + 1);
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
countInGroup('/', 0);
|
|
1002
|
+
return {
|
|
1003
|
+
totalGroups: this.listAllGroups().length + 1, // +1 for root
|
|
1004
|
+
totalVariables,
|
|
1005
|
+
totalAttributes,
|
|
1006
|
+
totalDimensions,
|
|
1007
|
+
maxDepth
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
// --------------------------------------------------
|
|
1011
|
+
// Heavy operations → still go to dataset
|
|
1012
|
+
// --------------------------------------------------
|
|
1013
|
+
async getVariableArray(variable, groupPath) {
|
|
1014
|
+
return this.dataset.getVariableArray(variable, groupPath);
|
|
1015
|
+
}
|
|
1016
|
+
async getSlicedVariableArray(variable, start, count, groupPath) {
|
|
1017
|
+
return this.dataset.getSlicedVariableArray(variable, start, count, groupPath);
|
|
1018
|
+
}
|
|
1019
|
+
async getVariableInfo(variable, groupPath) {
|
|
1020
|
+
return this.dataset.getVariableInfo(variable, groupPath);
|
|
1021
|
+
}
|
|
636
1022
|
}
|
|
637
1023
|
//# sourceMappingURL=netcdf4.js.map
|