Skip to content

Commit b0912ee

Browse files
committed
support subsetting the coefficients of an Axis object
1 parent 16f977d commit b0912ee

2 files changed

Lines changed: 102 additions & 0 deletions

File tree

tests/test_service.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,5 @@ def test_list_full_info():
128128
"covMetadata": null
129129
}'''
130130
assert str(cov) == expected
131+
subset = cov.bbox.ansi["2006-08-01" : "2007-01-01"]
132+
assert len(subset) == 6

wcs/model.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ class Axis:
110110
"""
111111
An axis with a name, low/upper bounds, a CRS, uom, resolution, coefficients.
112112
113+
A subset of the coefficients (axis coordinates) can be retrieved with the [] operator,
114+
e.g. for an irregular temporal axis: axis["2024-01-01" : "2024-01-31"].
115+
See :meth:`__getitem__` for more details.
116+
113117
:param name: Name of the axis.
114118
:param low: Lower bound of the axis.
115119
:param high: Upper bound of the axis.
@@ -155,6 +159,102 @@ def __str__(self):
155159
ret += f'{indent}coefficients: {coefficients}'
156160
return ret
157161

162+
def is_temporal(self) -> bool:
163+
"""
164+
Returns: True if this axis is a temporal axis (e.g. ansi), False otherwise.
165+
"""
166+
return isinstance(self.low, datetime)
167+
168+
def is_spatial(self) -> bool:
169+
"""
170+
Returns: True if this axis is a spatial axis (e.g. Lat, Lon, E, N), False otherwise.
171+
"""
172+
return not self.is_temporal()
173+
174+
def is_irregular(self) -> bool:
175+
"""
176+
Returns: True if this axis is an irregular axis, False otherwise.
177+
"""
178+
return self.coefficients is not None and len(self.coefficients) > 0
179+
180+
def is_regular(self) -> bool:
181+
"""
182+
Returns: True if this axis is a regular axis, False otherwise.
183+
"""
184+
return self.resolution is not None
185+
186+
def __getitem__(self, item) -> list[BoundType]:
187+
"""
188+
- If :attr:`coefficients` is not None, then they are subsetted according to ``item``
189+
- Otherwise, a list of coefficients is generated according to the :attr:`resolution`,
190+
between the start and stop provided by the item slice.
191+
192+
:param item: must be a :class:`slice` object with a start and stop set;
193+
the step is ignored. The start and stop must be valid coordinates in the
194+
axis :attr:`crs` and within the :attr:`low` / :attr:`high` bounds of this object.
195+
196+
:raises WCSClientException:
197+
- if :attr:`coefficients` and :attr:`resolution` are both None.
198+
- if ``item`` is not a slice object
199+
- if the start / stop of ``item`` are invalid coordinates
200+
"""
201+
if not isinstance(item, slice):
202+
raise WCSClientException(f"Invalid coordinates provided for operator [] "
203+
f"on axis {self.name}, expected a slice of the form start:stop.")
204+
if item.stop is None:
205+
raise WCSClientException(f"No upper limit provided for operator [] on axis {self.name}.")
206+
207+
temporal = self.is_temporal()
208+
regular = self.is_regular()
209+
irregular = self.is_irregular()
210+
211+
if not regular and not irregular:
212+
raise WCSClientException(f"operator [] is inapplicable to axis {self.name} "
213+
f"without a resolution or coefficients.")
214+
if temporal and not irregular:
215+
raise WCSClientException(f"operator [] is inapplicable to regular "
216+
f"temporal axis {self.name}.")
217+
218+
start = item.start
219+
stop = item.stop
220+
221+
# parse string datetime to datetimes if needed, and make sure all datetime have the same tzinfo
222+
if temporal:
223+
tz = self.coefficients[0].tzinfo
224+
if isinstance(start, str):
225+
start = datetime.fromisoformat(start)
226+
elif not isinstance(start, datetime):
227+
raise WCSClientException(f"Invalid type of start coordinate provided for operator [] "
228+
f"on axis {self.name}, expected either a string or a datetime.")
229+
if isinstance(stop, str):
230+
stop = datetime.fromisoformat(stop).replace(tzinfo=tz)
231+
elif not isinstance(stop, datetime):
232+
raise WCSClientException(f"Invalid type of stop coordinate provided for operator [] "
233+
f"on axis {self.name}, expected either a string or a datetime.")
234+
start = start.replace(tzinfo=tz)
235+
stop = stop.replace(tzinfo=tz)
236+
237+
coefficients = self.get_coefficients()
238+
return [c for c in coefficients if start <= c <= stop]
239+
240+
def get_coefficients(self) -> list[BoundType]:
241+
"""
242+
:return: a list of coefficients, automatically generated if this
243+
is a regular axis.
244+
"""
245+
if self.is_irregular():
246+
return self.coefficients
247+
if not self.is_regular():
248+
raise WCSClientException(f"{self.name} is not a regular or irregular "
249+
f"axis, cannot calculate coefficients.")
250+
251+
ret = []
252+
current = self.low
253+
while current <= self.high:
254+
ret.append(current)
255+
current += self.resolution
256+
return ret
257+
158258

159259
@dataclass
160260
class BoundingBox:

0 commit comments

Comments
 (0)