@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/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, this.ncid);
302
+ return NCGet.getDimCount(this.module, ncid);
300
303
  }
301
304
  }
302
- async getVariables() {
305
+ async getGroupVariables(groupPath) {
303
306
  if (this.worker) {
304
- return this.callWorker('getVariables');
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.getVariables(this.module, this.ncid);
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, this.ncid);
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, this.ncid);
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, this.ncid, dimid);
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, this.ncid);
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