Skip to content

Commit 2874f07

Browse files
committed
Making attribute names more precise: obsdim->obs_dimname, timedim->time_varname
1 parent dc6f354 commit 2874f07

File tree

7 files changed

+130
-134
lines changed

7 files changed

+130
-134
lines changed

examples/example_find_positions_at_obs_times.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
def gridwaves(tds):
2626
t = tds[['lat', 'lon',
2727
'time']].traj.gridtime(tds['time_waves_imu'].squeeze())
28-
return t.traj.to_2d(obsdim='obs_waves_imu')
28+
return t.traj.to_2d(obs_dimname='obs_waves_imu')
2929

3030

3131
dsw = ds.groupby('trajectory').map(gridwaves)

tests/test_read_sfy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ def test_interpret_sfy(test_data):
88
ds = xr.open_dataset(test_data / 'bug32.nc')
99
print(ds)
1010

11-
assert ds.traj.obsdim == 'package'
12-
assert ds.traj.timedim == 'position_time'
11+
assert ds.traj.obs_dimname == 'package'
12+
assert ds.traj.time_varname == 'position_time'
1313

1414
assert ds.traj.is_2d()
1515

trajan/accessor.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ def detect_tx_dim(ds):
2626
raise ValueError("Could not determine x / lon variable")
2727

2828

29-
def detect_time_dim(ds, obsdim):
30-
logger.debug(f'Detecting time-dimension for "{obsdim}"..')
29+
def detect_time_dim(ds, obs_dimname):
30+
logger.debug(f'Detecting time-dimension for "{obs_dimname}"..')
3131
for v in ds.variables:
32-
if obsdim in ds[v].dims and 'time' in v:
32+
if obs_dimname in ds[v].dims and 'time' in v:
3333
return v
3434

3535
raise ValueError("no time dimension detected")
@@ -47,8 +47,8 @@ def __new__(cls, ds):
4747
ds = ds.expand_dims({'trajectory': 1})
4848
ds['trajectory'].attrs['cf_role'] = 'trajectory_id'
4949

50-
obsdim = None
51-
timedim = None
50+
obs_dimname = None
51+
time_varname = None
5252

5353
tx = detect_tx_dim(ds)
5454

@@ -62,7 +62,7 @@ def __new__(cls, ds):
6262
# NOTE: this is probably not standard; something to point to the CF conventions?
6363
# NOTE: for now, there is no discovery of the "index" dim, this is hardcorded; any way to do better?
6464
if "index" in tx.dims:
65-
obsdim = "index"
65+
obs_dimname = "index"
6666

6767
# discover the timecoord variable name #######################
6868
# find all variables with standard_name "time"
@@ -90,63 +90,63 @@ def __new__(cls, ds):
9090
else:
9191
raise ValueError(f"cannot deduce rowsizevar; we have the following candidates: {with_dim_trajectory = }")
9292
# sanity check
93-
if not np.sum(ds[rowsizevar].to_numpy()) == len(ds[obsdim]):
93+
if not np.sum(ds[rowsizevar].to_numpy()) == len(ds[obs_dimname]):
9494
raise ValueError("mismatch between the index length and the sum of the deduced trajectory lengths")
9595

9696
logger.debug(
97-
f"1D storage dataset; detected: {obsdim = }, {timecoord = }, {trajectorycoord = }, {rowsizevar}"
97+
f"1D storage dataset; detected: {obs_dimname = }, {timecoord = }, {trajectorycoord = }, {rowsizevar}"
9898
)
9999

100-
return ocls(ds, obsdim, timecoord, trajectorycoord, rowsizevar)
100+
return ocls(ds, obs_dimname, timecoord, trajectorycoord, rowsizevar)
101101

102102
else:
103103
logging.warning(f"{ds} has {tx.dims = } which is of dimension 1 but is not index; this is a bit unusual; try to parse with Traj1d or Traj2d")
104104

105105
# we have a ds where 2D arrays are used to store data, this is either Traj1d or Traj2d
106106
# there may also be some slightly unusual cases where these Traj1d and Traj2d classes will be used on data with 1D arrays
107107
if 'obs' in tx.dims:
108-
obsdim = 'obs'
109-
timedim = detect_time_dim(ds, obsdim)
108+
obs_dimname = 'obs'
109+
time_varname = detect_time_dim(ds, obs_dimname)
110110

111111
elif 'index' in tx.dims:
112-
obsdim = 'obs'
113-
timedim = detect_time_dim(ds, obsdim)
112+
obs_dimname = 'obs'
113+
time_varname = detect_time_dim(ds, obs_dimname)
114114

115115
elif 'time' in tx.dims:
116-
obsdim = 'time'
117-
timedim = 'time'
116+
obs_dimname = 'time'
117+
time_varname = 'time'
118118

119119
else:
120120
for d in tx.dims:
121121
if not ds[d].attrs.get(
122122
'cf_role',
123123
None) == 'trajectory_id' and not 'traj' in d:
124124

125-
obsdim = d
126-
timedim = detect_time_dim(ds, obsdim)
125+
obs_dimname = d
126+
time_varname = detect_time_dim(ds, obs_dimname)
127127

128128
break
129129

130-
if obsdim is None:
130+
if obs_dimname is None:
131131
logger.warning('No time or obs dimension detected.')
132132

133133
logger.debug(
134-
f"Detected obs-dim: {obsdim}, detected time-dim: {timedim}.")
134+
f"Detected obs-dim: {obs_dimname}, detected time-dim: {time_varname}.")
135135

136-
if obsdim is None:
136+
if obs_dimname is None:
137137
ocls = Traj1d
138138

139-
elif len(ds[timedim].shape) <= 1:
139+
elif len(ds[time_varname].shape) <= 1:
140140
logger.debug('Detected structured (1D) trajectory dataset')
141141
ocls = Traj1d
142142

143-
elif len(ds[timedim].shape) == 2:
143+
elif len(ds[time_varname].shape) == 2:
144144
logger.debug('Detected un-structured (2D) trajectory dataset')
145145
ocls = Traj2d
146146

147147
else:
148148
raise ValueError(
149-
f'Time dimension has shape greater than 2: {ds["timedim"].shape}'
149+
f'Time dimension has shape greater than 2: {ds["time_varname"].shape}'
150150
)
151151

152-
return ocls(ds, obsdim, timedim)
152+
return ocls(ds, obs_dimname, time_varname)

trajan/ragged.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ class ContiguousRagged(Traj):
1414
trajdim: str
1515
rowvar: str
1616

17-
def __init__(self, ds, obsdim, timedim, trajectorycoord, rowsizevar):
17+
def __init__(self, ds, obs_dimname, time_varname, trajectorycoord, rowsizevar):
1818
self.trajdim = trajectorycoord
1919
self.rowvar = rowsizevar
20-
super().__init__(ds, obsdim, timedim)
20+
super().__init__(ds, obs_dimname, time_varname)
2121

22-
def to_2d(self, obsdim='obs'):
22+
def to_2d(self, obs_dimname='obs'):
2323
"""This actually converts a contiguous ragged xarray Dataset into an xarray Dataset that follows the Traj2d conventions."""
2424
global_attrs = self.ds.attrs
2525

@@ -45,7 +45,7 @@ def to_2d(self, obsdim='obs'):
4545
self.ds[self.rowvar].to_numpy()):
4646
end_index = start_index + crrt_rowsize
4747
array_time[crrt_index, :crrt_rowsize] = self.ds[
48-
self.timedim][start_index:end_index]
48+
self.time_varname][start_index:end_index]
4949
start_index = end_index
5050

5151
# it seems that we need to build the "backbone" of the Dataset independently first
@@ -70,7 +70,7 @@ def to_2d(self, obsdim='obs'):
7070

7171
# trajectory vars
7272
'time':
73-
xr.DataArray(dims=["trajectory", obsdim],
73+
xr.DataArray(dims=["trajectory", obs_dimname],
7474
data=array_time,
7575
attrs={
7676
"standard_name": "time",
@@ -81,7 +81,7 @@ def to_2d(self, obsdim='obs'):
8181

8282
# now add all "normal" variables
8383
# NOTE: for now, we only consider scalar vars; if we want to consider more complex vars (e.g., spectra), this will need updated
84-
# NOTE: such an update would typically need to look at the dims of the variable, and if there are additional dims to obsdim, create a higer dim variable
84+
# NOTE: such an update would typically need to look at the dims of the variable, and if there are additional dims to obs_dimname, create a higer dim variable
8585

8686
for crrt_data_var in self.ds.data_vars:
8787
attrs = self.ds[crrt_data_var].attrs
@@ -90,9 +90,9 @@ def to_2d(self, obsdim='obs'):
9090
continue
9191

9292
if len(self.ds[crrt_data_var].dims
93-
) != 1 or self.ds[crrt_data_var].dims[0] != self.obsdim:
93+
) != 1 or self.ds[crrt_data_var].dims[0] != self.obs_dimname:
9494
raise ValueError(
95-
f"data_vars element {crrt_data_var} has dims {self.ds[crrt_data_var].dims}, expected {(self.obsdim,)}"
95+
f"data_vars element {crrt_data_var} has dims {self.ds[crrt_data_var].dims}, expected {(self.obs_dimname,)}"
9696
)
9797

9898
crrt_var = np.full((nbr_trajectories, longest_trajectory), np.nan)
@@ -119,7 +119,7 @@ def to_2d(self, obsdim='obs'):
119119
crrt_data_var = "lat"
120120

121121
ds_converted_to_traj2d[crrt_data_var] = \
122-
xr.DataArray(dims=["trajectory", obsdim],
122+
xr.DataArray(dims=["trajectory", obs_dimname],
123123
data=crrt_var,
124124
attrs=attrs)
125125

@@ -140,6 +140,6 @@ def plot(self) -> Plot:
140140
def timestep(self, average=np.median):
141141
return self.to_2d().traj.timestep(average)
142142

143-
def gridtime(self, times, timedim=None, round=True):
144-
return self.to_2d().traj.gridtime(times, timedim, round)
143+
def gridtime(self, times, time_varname=None, round=True):
144+
return self.to_2d().traj.gridtime(times, time_varname, round)
145145

trajan/traj.py

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ def detect_tx_dim(ds):
3333
raise ValueError("Could not determine x / lon variable")
3434

3535

36-
def detect_time_dim(ds, obsdim):
37-
logger.debug(f'Detecting time-dimension for "{obsdim}"..')
36+
def detect_time_dim(ds, obs_dimname):
37+
logger.debug(f'Detecting time-dimension for "{obs_dimname}"..')
3838
for v in ds.variables:
39-
if obsdim in ds[v].dims and 'time' in v:
39+
if obs_dimname in ds[v].dims and 'time' in v:
4040
return v
4141

4242
raise ValueError("no time dimension detected")
@@ -50,28 +50,24 @@ class Traj:
5050

5151
__gcrs__: pyproj.CRS
5252

53-
obsdim: str
54-
"""
55-
Name of the dimension along which observations are taken. Usually either `obs` or `time`.
56-
"""
57-
timedim: str
58-
59-
def __init__(self, ds, obsdim, timedim):
53+
def __init__(self, ds, obs_dimname, time_varname):
6054
self.ds = ds
6155
self.__plot__ = None
6256
self.__animate__ = None
6357
self.__gcrs__ = pyproj.CRS.from_epsg(4326)
64-
self.obsdim = obsdim
65-
self.timedim = timedim
58+
self.obs_dimname = obs_dimname # dimension along which time increases
59+
self.time_varname = time_varname
6660

6761
def __repr__(self):
6862
output = '=======================\n'
6963
output += 'TrajAn info:\n'
7064
output += '------------\n'
7165
output += f'{self.ds.sizes["trajectory"]} trajectories\n'
7266
if 'time' in self.ds.variables:
73-
if self.timedim in self.ds.sizes:
74-
output += f'{self.ds.sizes[self.timedim]} timesteps\n'
67+
if self.time_varname in self.ds.sizes:
68+
output += f'{self.ds.sizes[self.time_varname]} timesteps'
69+
timevar = self.ds[self.time_varname]
70+
output += f' {timevar.name}{list(timevar.sizes)} ({len(timevar.sizes)}D)\n'
7571
try:
7672
timestep = self.timestep()
7773
timestep = timedelta(seconds=int(timestep))
@@ -532,18 +528,18 @@ def distance_to_next(self):
532528

533529
lon = self.ds.lon
534530
lat = self.ds.lat
535-
lenobs = self.ds.sizes[self.obsdim]
536-
lonfrom = lon.isel({self.obsdim: slice(0, lenobs - 1)})
537-
latfrom = lat.isel({self.obsdim: slice(0, lenobs - 1)})
538-
lonto = lon.isel({self.obsdim: slice(1, lenobs)})
539-
latto = lat.isel({self.obsdim: slice(1, lenobs)})
531+
lenobs = self.ds.sizes[self.obs_dimname]
532+
lonfrom = lon.isel({self.obs_dimname: slice(0, lenobs - 1)})
533+
latfrom = lat.isel({self.obs_dimname: slice(0, lenobs - 1)})
534+
lonto = lon.isel({self.obs_dimname: slice(1, lenobs)})
535+
latto = lat.isel({self.obs_dimname: slice(1, lenobs)})
540536
geod = pyproj.Geod(ellps='WGS84')
541537
azimuth_forward, a2, distance = geod.inv(lonfrom, latfrom, lonto,
542538
latto)
543539

544540
distance = xr.DataArray(distance, coords=lonfrom.coords, dims=lon.dims)
545-
distance = xr.concat((distance, distance.isel({self.obsdim: -1})),
546-
dim=self.obsdim) # repeating last time step to
541+
distance = xr.concat((distance, distance.isel({self.obs_dimname: -1})),
542+
dim=self.obs_dimname) # repeating last time step to
547543
return distance
548544

549545
def azimuth_to_next(self):
@@ -564,11 +560,11 @@ def azimuth_to_next(self):
564560
# TODO: method is almost duplicate of "distance_to_next" above
565561
lon = self.ds.lon
566562
lat = self.ds.lat
567-
lenobs = self.ds.dims[self.obsdim]
568-
lonfrom = lon.isel({self.obsdim: slice(0, lenobs - 1)})
569-
latfrom = lat.isel({self.obsdim: slice(0, lenobs - 1)})
570-
lonto = lon.isel({self.obsdim: slice(1, lenobs)})
571-
latto = lat.isel({self.obsdim: slice(1, lenobs)})
563+
lenobs = self.ds.dims[self.obs_dimname]
564+
lonfrom = lon.isel({self.obs_dimname: slice(0, lenobs - 1)})
565+
latfrom = lat.isel({self.obs_dimname: slice(0, lenobs - 1)})
566+
lonto = lon.isel({self.obs_dimname: slice(1, lenobs)})
567+
latto = lat.isel({self.obs_dimname: slice(1, lenobs)})
572568
geod = pyproj.Geod(ellps='WGS84')
573569
azimuth_forward, a2, distance = geod.inv(lonfrom, latfrom, lonto,
574570
latto)
@@ -577,8 +573,8 @@ def azimuth_to_next(self):
577573
coords=lonfrom.coords,
578574
dims=lon.dims)
579575
azimuth_forward = xr.concat(
580-
(azimuth_forward, azimuth_forward.isel({self.obsdim: -1})),
581-
dim=self.obsdim) # repeating last time step to
576+
(azimuth_forward, azimuth_forward.isel({self.obs_dimname: -1})),
577+
dim=self.obs_dimname) # repeating last time step to
582578
return azimuth_forward
583579

584580
def velocity_components(self):
@@ -679,7 +675,7 @@ def get_area_convex_hull(self):
679675
return np.array(hull.volume) # volume=area for 2D as here
680676

681677
@abstractmethod
682-
def gridtime(self, times, timedim=None) -> xr.Dataset:
678+
def gridtime(self, times, time_varname=None) -> xr.Dataset:
683679
"""Interpolate dataset to a regular time interval or a different grid.
684680
685681
Parameters
@@ -689,7 +685,7 @@ def gridtime(self, times, timedim=None) -> xr.Dataset:
689685
- an array of times, or
690686
- a string specifying a Pandas daterange (e.g. 'h', '6h, 'D'...) suitable for `pd.date_range`.
691687
692-
timedim : str
688+
time_varname : str
693689
Name of new time dimension. The default is to use the same name as previously.
694690
695691
Returns
@@ -894,7 +890,7 @@ def condense_obs(self) -> xr.Dataset:
894890
"""
895891

896892
@abstractmethod
897-
def to_2d(self, obsdim='obs') -> xr.Dataset:
893+
def to_2d(self, obs_dimname='obs') -> xr.Dataset:
898894
"""
899895
Convert dataset into a 2D dataset from.
900896
"""

0 commit comments

Comments
 (0)