Skip to content

Commit 464be7c

Browse files
committed
[feat] add xarray coords support besides dims
1 parent 2e76ccd commit 464be7c

File tree

3 files changed

+255
-1
lines changed

3 files changed

+255
-1
lines changed

jdict.m

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,12 @@
386386
if (~isempty(dimpos) && ~isempty(idxkey(i + 1).subs))
387387
nddata = length(dims);
388388
indices = repmat({':'}, 1, nddata);
389-
indices{dimpos(1)} = idxkey(i + 1).subs{1};
389+
coords = obj.getattr(trackpath, 'coords');
390+
if (~isempty(coords) && isstruct(coords) && isfield(coords, onekey))
391+
indices{dimpos(1)} = coordlookup_(coords.(onekey), idxkey(i + 1).subs{1}, onekey);
392+
else
393+
indices{dimpos(1)} = idxkey(i + 1).subs{1};
394+
end
390395
subsargs = struct('type', '()', 'subs', {indices});
391396
val = subsref(val, subsargs);
392397
newobj = jdict(val);
@@ -1415,3 +1420,43 @@
14151420
end
14161421
end
14171422
end
1423+
1424+
% convert coord labels to indices (vectorized)
1425+
function idx = coordlookup_(coords, sel, dim)
1426+
if isnumeric(coords) && isnumeric(sel)
1427+
[ok, idx] = ismember(sel, coords);
1428+
if ~all(ok)
1429+
error('Coord not found in "%s"', dim);
1430+
end
1431+
elseif isnumeric(sel)
1432+
idx = sel;
1433+
elseif iscell(sel)
1434+
idx = cellfun(@(v) findcoord_(coords, v, dim), sel);
1435+
elseif isstruct(sel) && isfield(sel, 'start')
1436+
s = 1;
1437+
e = length(coords);
1438+
if ~isempty(sel.start)
1439+
s = findcoord_(coords, sel.start, dim);
1440+
end
1441+
if isfield(sel, 'stop') && ~isempty(sel.stop)
1442+
e = findcoord_(coords, sel.stop, dim);
1443+
end
1444+
idx = s:e;
1445+
else
1446+
idx = findcoord_(coords, sel, dim);
1447+
end
1448+
end
1449+
1450+
% find single coord index
1451+
function idx = findcoord_(coords, val, dim)
1452+
if isnumeric(coords)
1453+
idx = find(coords == val, 1);
1454+
elseif iscell(coords)
1455+
idx = find(cellfun(@(c) isequal(c, val), coords), 1);
1456+
else
1457+
idx = find(coords == val, 1);
1458+
end
1459+
if isempty(idx)
1460+
error('Coord "%s" not found in "%s"', mat2str(val), dim);
1461+
end
1462+
end

jsonlab.prj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ Please note that data files produced by `saveubjson` may utilize a special "opti
123123
<file>${PROJECT_ROOT}/jsonset.m</file>
124124
<file>${PROJECT_ROOT}/loadbj.m</file>
125125
<file>${PROJECT_ROOT}/loadjd.m</file>
126+
<file>${PROJECT_ROOT}/loadyaml.m</file>
127+
<file>${PROJECT_ROOT}/saveyaml.m</file>
126128
<file>${PROJECT_ROOT}/loadjson.m</file>
127129
<file>${PROJECT_ROOT}/loadmsgpack.m</file>
128130
<file>${PROJECT_ROOT}/loadubjson.m</file>

test/run_jsonlab_test.m

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,6 +1959,213 @@ function run_jsonlab_test(tests)
19591959
jd8{'flag'} = true;
19601960
end
19611961
test_jsonlab('test multiple attributes different types', @savejson, {jd8{'dims'}, jd8{'units'}, jd8{'count'}, jd8{'flag'}}, '[["time","space"],"meters",42,true]', 'compact', 1);
1962+
1963+
fprintf(sprintf('%s\n', char(ones(1, 79) * 61)));
1964+
fprintf('Test jdict xarray-like coords\n');
1965+
fprintf(sprintf('%s\n', char(ones(1, 79) * 61)));
1966+
1967+
% Test 1: Root level string coords selection
1968+
jd1 = jdict(reshape(1:12, [3, 4]));
1969+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
1970+
jd1.setattr('dims', {'x', 'y'});
1971+
jd1.setattr('coords', struct('x', {{'a', 'b', 'c'}}, 'y', {{'p', 'q', 'r', 's'}}));
1972+
else
1973+
jd1{'dims'} = {'x', 'y'};
1974+
jd1{'coords'} = struct('x', {{'a', 'b', 'c'}}, 'y', {{'p', 'q', 'r', 's'}});
1975+
end
1976+
test_jsonlab('string coord single select x', @savejson, jd1.x('b').v(), '[2,5,8,11]', 'compact', 1);
1977+
test_jsonlab('string coord single select y', @savejson, jd1.y('r').v(), '[[7],[8],[9]]', 'compact', 1);
1978+
1979+
% Test 2: Root level numeric coords selection
1980+
jd2 = jdict(reshape(1:12, [3, 4]));
1981+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
1982+
jd2.setattr('dims', {'x', 'y'});
1983+
jd2.setattr('coords', struct('x', [10, 20, 30], 'y', [100, 200, 300, 400]));
1984+
else
1985+
jd2{'dims'} = {'x', 'y'};
1986+
jd2{'coords'} = struct('x', [10, 20, 30], 'y', [100, 200, 300, 400]);
1987+
end
1988+
test_jsonlab('numeric coord single select x', @savejson, jd2.x(20).v(), '[2,5,8,11]', 'compact', 1);
1989+
test_jsonlab('numeric coord single select y', @savejson, jd2.y(300).v(), '[[7],[8],[9]]', 'compact', 1);
1990+
1991+
% Test 3: Multiple string coords selection
1992+
jd3 = jdict(reshape(1:12, [3, 4]));
1993+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
1994+
jd3.setattr('dims', {'x', 'y'});
1995+
jd3.setattr('coords', struct('x', {{'a', 'b', 'c'}}, 'y', {{'p', 'q', 'r', 's'}}));
1996+
else
1997+
jd3{'dims'} = {'x', 'y'};
1998+
jd3{'coords'} = struct('x', {{'a', 'b', 'c'}}, 'y', {{'p', 'q', 'r', 's'}});
1999+
end
2000+
test_jsonlab('string coord multi select x', @savejson, jd3.x({'a', 'c'}).v(), '[[1,4,7,10],[3,6,9,12]]', 'compact', 1);
2001+
test_jsonlab('string coord multi select y', @savejson, jd3.y({'p', 's'}).v(), '[[1,10],[2,11],[3,12]]', 'compact', 1);
2002+
2003+
% Test 4: Multiple numeric coords selection
2004+
jd4 = jdict(reshape(1:12, [3, 4]));
2005+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2006+
jd4.setattr('dims', {'x', 'y'});
2007+
jd4.setattr('coords', struct('x', [10, 20, 30], 'y', [100, 200, 300, 400]));
2008+
else
2009+
jd4{'dims'} = {'x', 'y'};
2010+
jd4{'coords'} = struct('x', [10, 20, 30], 'y', [100, 200, 300, 400]);
2011+
end
2012+
test_jsonlab('numeric coord multi select x', @savejson, jd4.x([10, 30]).v(), '[[1,4,7,10],[3,6,9,12]]', 'compact', 1);
2013+
test_jsonlab('numeric coord multi select y', @savejson, jd4.y([100, 400]).v(), '[[1,10],[2,11],[3,12]]', 'compact', 1);
2014+
2015+
% Test 5: Slice selection with struct
2016+
jd5 = jdict(reshape(1:12, [3, 4]));
2017+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2018+
jd5.setattr('dims', {'x', 'y'});
2019+
jd5.setattr('coords', struct('x', {{'a', 'b', 'c'}}, 'y', {{'p', 'q', 'r', 's'}}));
2020+
else
2021+
jd5{'dims'} = {'x', 'y'};
2022+
jd5{'coords'} = struct('x', {{'a', 'b', 'c'}}, 'y', {{'p', 'q', 'r', 's'}});
2023+
end
2024+
test_jsonlab('string coord slice x', @savejson, jd5.x(struct('start', 'a', 'stop', 'b')).v(), '[[1,4,7,10],[2,5,8,11]]', 'compact', 1);
2025+
test_jsonlab('string coord slice y', @savejson, jd5.y(struct('start', 'q', 'stop', 's')).v(), '[[4,7,10],[5,8,11],[6,9,12]]', 'compact', 1);
2026+
2027+
% Test 6: Second level coords
2028+
jd6 = jdict(struct('data', reshape(1:12, [3, 4])));
2029+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2030+
jd6.('data').setattr('dims', {'row', 'col'});
2031+
jd6.('data').setattr('coords', struct('row', {{'r1', 'r2', 'r3'}}, 'col', [1, 2, 3, 4]));
2032+
else
2033+
jd6.('data'){'dims'} = {'row', 'col'};
2034+
jd6.('data'){'coords'} = struct('row', {{'r1', 'r2', 'r3'}}, 'col', [1, 2, 3, 4]);
2035+
end
2036+
test_jsonlab('second level string coord', @savejson, jd6.('data').row('r2').v(), '[2,5,8,11]', 'compact', 1);
2037+
test_jsonlab('second level numeric coord', @savejson, jd6.('data').col(3).v(), '[[7],[8],[9]]', 'compact', 1);
2038+
2039+
% Test 7: Third level nested coords
2040+
jd7 = jdict(struct('level1', struct('level2', struct('arr', reshape(1:6, [2, 3])))));
2041+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2042+
jd7.('level1').('level2').('arr').setattr('dims', {'i', 'j'});
2043+
jd7.('level1').('level2').('arr').setattr('coords', struct('i', {{'x', 'y'}}, 'j', {{'a', 'b', 'c'}}));
2044+
else
2045+
jd7.('level1').('level2').('arr'){'dims'} = {'i', 'j'};
2046+
jd7.('level1').('level2').('arr'){'coords'} = struct('i', {{'x', 'y'}}, 'j', {{'a', 'b', 'c'}});
2047+
end
2048+
test_jsonlab('third level coord select i', @savejson, jd7.('level1').('level2').('arr').i('y').v(), '[2,4,6]', 'compact', 1);
2049+
test_jsonlab('third level coord select j', @savejson, jd7.('level1').('level2').('arr').j('b').v(), '[[3],[4]]', 'compact', 1);
2050+
2051+
% Test 8: Sibling arrays with independent coords
2052+
jd8 = jdict(struct('exp1', reshape(1:6, [2, 3]), 'exp2', reshape(1:8, [2, 4])));
2053+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2054+
jd8.('exp1').setattr('dims', {'t', 's'});
2055+
jd8.('exp1').setattr('coords', struct('t', {{'t1', 't2'}}, 's', {{'s1', 's2', 's3'}}));
2056+
jd8.('exp2').setattr('dims', {'x', 'y'});
2057+
jd8.('exp2').setattr('coords', struct('x', [0, 1], 'y', [10, 20, 30, 40]));
2058+
else
2059+
jd8.('exp1'){'dims'} = {'t', 's'};
2060+
jd8.('exp1'){'coords'} = struct('t', {{'t1', 't2'}}, 's', {{'s1', 's2', 's3'}});
2061+
jd8.('exp2'){'dims'} = {'x', 'y'};
2062+
jd8.('exp2'){'coords'} = struct('x', [0, 1], 'y', [10, 20, 30, 40]);
2063+
end
2064+
test_jsonlab('sibling coord select exp1', @savejson, jd8.('exp1').t('t2').v(), '[2,4,6]', 'compact', 1);
2065+
test_jsonlab('sibling coord select exp2', @savejson, jd8.('exp2').y(30).v(), '[[5],[6]]', 'compact', 1);
2066+
2067+
% Test 9: Direct index when coords defined (numeric index on string coords)
2068+
jd9 = jdict(reshape(1:12, [3, 4]));
2069+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2070+
jd9.setattr('dims', {'x', 'y'});
2071+
jd9.setattr('coords', struct('x', {{'a', 'b', 'c'}}, 'y', {{'p', 'q', 'r', 's'}}));
2072+
else
2073+
jd9{'dims'} = {'x', 'y'};
2074+
jd9{'coords'} = struct('x', {{'a', 'b', 'c'}}, 'y', {{'p', 'q', 'r', 's'}});
2075+
end
2076+
test_jsonlab('direct index with string coords', @savejson, jd9.x(2).v(), '[2,5,8,11]', 'compact', 1);
2077+
2078+
% Test 10: Coords without dims should not break indexing
2079+
jd10 = jdict(reshape(1:12, [3, 4]));
2080+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2081+
jd10.setattr('dims', {'x', 'y'});
2082+
else
2083+
jd10{'dims'} = {'x', 'y'};
2084+
end
2085+
test_jsonlab('dims without coords still works', @savejson, jd10.x(2).v(), '[2,5,8,11]', 'compact', 1);
2086+
2087+
% Test 11: 3D array coords
2088+
jd11 = jdict(reshape(1:24, [2, 3, 4]));
2089+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2090+
jd11.setattr('dims', {'x', 'y', 'z'});
2091+
jd11.setattr('coords', struct('x', {{'a', 'b'}}, 'y', [10, 20, 30], 'z', {{'p', 'q', 'r', 's'}}));
2092+
else
2093+
jd11{'dims'} = {'x', 'y', 'z'};
2094+
jd11{'coords'} = struct('x', {{'a', 'b'}}, 'y', [10, 20, 30], 'z', {{'p', 'q', 'r', 's'}});
2095+
end
2096+
test_jsonlab('3D coord select dim1', @savejson, jd11.x('b').v(), '{"_ArrayType_":"double","_ArraySize_":[1,3,4],"_ArrayData_":[2,8,14,20,4,10,16,22,6,12,18,24]}', 'compact', 1);
2097+
test_jsonlab('3D coord select dim2', @savejson, jd11.y(20).v(), '{"_ArrayType_":"double","_ArraySize_":[2,1,4],"_ArrayData_":[3,9,15,21,4,10,16,22]}', 'compact', 1);
2098+
test_jsonlab('3D coord select dim3', @savejson, jd11.z('r').v(), '[[13,15,17],[14,16,18]]', 'compact', 1);
2099+
2100+
% Test 12: getattr returns coords correctly
2101+
jd12 = jdict(rand(3, 4));
2102+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2103+
jd12.setattr('dims', {'x', 'y'});
2104+
jd12.setattr('coords', struct('x', {{'a', 'b', 'c'}}, 'y', [1, 2, 3, 4]));
2105+
else
2106+
jd12{'dims'} = {'x', 'y'};
2107+
jd12{'coords'} = struct('x', {{'a', 'b', 'c'}}, 'y', [1, 2, 3, 4]);
2108+
end
2109+
test_jsonlab('getattr returns coords struct', @savejson, jd12{'coords'}, '{"x":["a","b","c"],"y":[1,2,3,4]}', 'compact', 1);
2110+
2111+
% Test 13: Cascade two single selections
2112+
jd13 = jdict(reshape(1:12, [3, 4]));
2113+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2114+
jd13.setattr('dims', {'x', 'y'});
2115+
jd13.setattr('coords', struct('x', {{'a', 'b', 'c'}}, 'y', {{'p', 'q', 'r', 's'}}));
2116+
else
2117+
jd13{'dims'} = {'x', 'y'};
2118+
jd13{'coords'} = struct('x', {{'a', 'b', 'c'}}, 'y', {{'p', 'q', 'r', 's'}});
2119+
end
2120+
test_jsonlab('cascade x then y', @savejson, jd13.x('b').y('r').v(), '[8]', 'compact', 1);
2121+
test_jsonlab('cascade y then x', @savejson, jd13.y('q').x('c').v(), '[6]', 'compact', 1);
2122+
2123+
% Test 14: Cascade multi-select then single
2124+
jd14 = jdict(reshape(1:12, [3, 4]));
2125+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2126+
jd14.setattr('dims', {'x', 'y'});
2127+
jd14.setattr('coords', struct('x', {{'a', 'b', 'c'}}, 'y', {{'p', 'q', 'r', 's'}}));
2128+
else
2129+
jd14{'dims'} = {'x', 'y'};
2130+
jd14{'coords'} = struct('x', {{'a', 'b', 'c'}}, 'y', {{'p', 'q', 'r', 's'}});
2131+
end
2132+
test_jsonlab('cascade multi then single', @savejson, jd14.x({'a', 'c'}).y('s').v(), '[[10],[12]]', 'compact', 1);
2133+
test_jsonlab('cascade single then multi', @savejson, jd14.y('p').x({'a', 'b'}).v(), '[[1],[2]]', 'compact', 1);
2134+
2135+
% Test 15: Cascade with numeric coords
2136+
jd15 = jdict(reshape(1:12, [3, 4]));
2137+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2138+
jd15.setattr('dims', {'x', 'y'});
2139+
jd15.setattr('coords', struct('x', [10, 20, 30], 'y', [100, 200, 300, 400]));
2140+
else
2141+
jd15{'dims'} = {'x', 'y'};
2142+
jd15{'coords'} = struct('x', [10, 20, 30], 'y', [100, 200, 300, 400]);
2143+
end
2144+
test_jsonlab('cascade numeric coords', @savejson, jd15.x(20).y(300).v(), '[8]', 'compact', 1);
2145+
test_jsonlab('cascade numeric multi then single', @savejson, jd15.x([10, 30]).y(400).v(), '[[10],[12]]', 'compact', 1);
2146+
2147+
% Test 16: Cascade in nested struct
2148+
jd16 = jdict(struct('data', reshape(1:12, [3, 4])));
2149+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2150+
jd16.('data').setattr('dims', {'row', 'col'});
2151+
jd16.('data').setattr('coords', struct('row', {{'r1', 'r2', 'r3'}}, 'col', {{'c1', 'c2', 'c3', 'c4'}}));
2152+
else
2153+
jd16.('data'){'dims'} = {'row', 'col'};
2154+
jd16.('data'){'coords'} = struct('row', {{'r1', 'r2', 'r3'}}, 'col', {{'c1', 'c2', 'c3', 'c4'}});
2155+
end
2156+
test_jsonlab('cascade in nested struct', @savejson, jd16.('data').row('r2').col('c3').v(), '[8]', 'compact', 1);
2157+
2158+
% Test 17: Cascade on 3D array
2159+
jd17 = jdict(reshape(1:24, [2, 3, 4]));
2160+
if (exist('OCTAVE_VERSION', 'builtin') ~= 0)
2161+
jd17.setattr('dims', {'x', 'y', 'z'});
2162+
jd17.setattr('coords', struct('x', {{'a', 'b'}}, 'y', {{'p', 'q', 'r'}}, 'z', {{'i', 'j', 'k', 'l'}}));
2163+
else
2164+
jd17{'dims'} = {'x', 'y', 'z'};
2165+
jd17{'coords'} = struct('x', {{'a', 'b'}}, 'y', {{'p', 'q', 'r'}}, 'z', {{'i', 'j', 'k', 'l'}});
2166+
end
2167+
test_jsonlab('3D cascade two dims', @savejson, jd17.x('b').z('k').v(), '[14,16,18]', 'compact', 1);
2168+
test_jsonlab('3D cascade all three dims', @savejson, jd17.x('a').y('q').z('j').v(), '[9]', 'compact', 1);
19622169
end
19632170

19642171
%%

0 commit comments

Comments
 (0)