pystac-ext-datacube 2.2.0__py3-none-any.whl
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.
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
"""Implements the :stac-ext:`Datacube Extension <datacube>`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from typing import Any, Generic, Literal, TypeVar, cast
|
|
8
|
+
|
|
9
|
+
import pystac
|
|
10
|
+
from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension
|
|
11
|
+
from pystac.extensions.hooks import ExtensionHooks
|
|
12
|
+
from pystac.utils import StringEnum, get_required, map_opt
|
|
13
|
+
|
|
14
|
+
#: Generalized version of :class:`~pystac.Collection`, `:class:`~pystac.Item`,
|
|
15
|
+
#: :class:`~pystac.Asset`, or :class:`~pystac.ItemAssetDefinition`
|
|
16
|
+
T = TypeVar(
|
|
17
|
+
"T", pystac.Collection, pystac.Item, pystac.Asset, pystac.ItemAssetDefinition
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
SCHEMA_URI = "https://stac-extensions.github.io/datacube/v2.2.0/schema.json"
|
|
21
|
+
|
|
22
|
+
PREFIX: str = "cube:"
|
|
23
|
+
DIMENSIONS_PROP = PREFIX + "dimensions"
|
|
24
|
+
VARIABLES_PROP = PREFIX + "variables"
|
|
25
|
+
|
|
26
|
+
# Dimension properties
|
|
27
|
+
DIM_TYPE_PROP = "type"
|
|
28
|
+
DIM_DESC_PROP = "description"
|
|
29
|
+
DIM_AXIS_PROP = "axis"
|
|
30
|
+
DIM_EXTENT_PROP = "extent"
|
|
31
|
+
DIM_VALUES_PROP = "values"
|
|
32
|
+
DIM_STEP_PROP = "step"
|
|
33
|
+
DIM_REF_SYS_PROP = "reference_system"
|
|
34
|
+
DIM_UNIT_PROP = "unit"
|
|
35
|
+
|
|
36
|
+
# Variable properties
|
|
37
|
+
VAR_TYPE_PROP = "type"
|
|
38
|
+
VAR_DESC_PROP = "description"
|
|
39
|
+
VAR_EXTENT_PROP = "extent"
|
|
40
|
+
VAR_VALUES_PROP = "values"
|
|
41
|
+
VAR_DIMENSIONS_PROP = "dimensions"
|
|
42
|
+
VAR_UNIT_PROP = "unit"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DimensionType(StringEnum):
|
|
46
|
+
"""Dimension object types for spatial and temporal Dimension Objects."""
|
|
47
|
+
|
|
48
|
+
SPATIAL = "spatial"
|
|
49
|
+
GEOMETRIES = "geometries"
|
|
50
|
+
TEMPORAL = "temporal"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class HorizontalSpatialDimensionAxis(StringEnum):
|
|
54
|
+
"""Allowed values for ``axis`` field of :class:`HorizontalSpatialDimension`
|
|
55
|
+
object."""
|
|
56
|
+
|
|
57
|
+
X = "x"
|
|
58
|
+
Y = "y"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class VerticalSpatialDimensionAxis(StringEnum):
|
|
62
|
+
"""Allowed values for ``axis`` field of :class:`VerticalSpatialDimension`
|
|
63
|
+
object."""
|
|
64
|
+
|
|
65
|
+
Z = "z"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Dimension(ABC):
|
|
69
|
+
"""Object representing a dimension of the datacube. The fields contained in
|
|
70
|
+
Dimension Object vary by ``type``. See the :stac-ext:`Datacube Dimension Object
|
|
71
|
+
<datacube#dimension-object>` docs for details.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
properties: dict[str, Any]
|
|
75
|
+
|
|
76
|
+
def __init__(self, properties: dict[str, Any]) -> None:
|
|
77
|
+
self.properties = properties
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def dim_type(self) -> DimensionType | str:
|
|
81
|
+
"""The type of the dimension. Must be ``"spatial"`` for
|
|
82
|
+
:stac-ext:`Horizontal Spatial Dimension Objects
|
|
83
|
+
<datacube#horizontal-spatial-raster-dimension-object>` or
|
|
84
|
+
:stac-ext:`Vertical Spatial Dimension Objects
|
|
85
|
+
<datacube#vertical-spatial-dimension-object>`, ``geometries`` for
|
|
86
|
+
:stac-ext:`Spatial Vector Dimension Objects
|
|
87
|
+
<datacube#spatial-vector-dimension-object>` ``"temporal"`` for
|
|
88
|
+
:stac-ext:`Temporal Dimension Objects
|
|
89
|
+
<datacube#temporal-dimension-object>`. May be an arbitrary string for
|
|
90
|
+
:stac-ext:`Additional Dimension Objects
|
|
91
|
+
<datacube#additional-dimension-object>`."""
|
|
92
|
+
return cast(
|
|
93
|
+
str,
|
|
94
|
+
get_required(
|
|
95
|
+
self.properties.get(DIM_TYPE_PROP), "cube:dimension", DIM_TYPE_PROP
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
@dim_type.setter
|
|
100
|
+
def dim_type(self, v: DimensionType | str) -> None:
|
|
101
|
+
self.properties[DIM_TYPE_PROP] = v
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def description(self) -> str | None:
|
|
105
|
+
"""Detailed multi-line description to explain the dimension. `CommonMark 0.29
|
|
106
|
+
<http://commonmark.org/>`__ syntax MAY be used for rich text representation."""
|
|
107
|
+
return self.properties.get(DIM_DESC_PROP)
|
|
108
|
+
|
|
109
|
+
@description.setter
|
|
110
|
+
def description(self, v: str | None) -> None:
|
|
111
|
+
if v is None:
|
|
112
|
+
self.properties.pop(DIM_DESC_PROP, None)
|
|
113
|
+
else:
|
|
114
|
+
self.properties[DIM_DESC_PROP] = v
|
|
115
|
+
|
|
116
|
+
def to_dict(self) -> dict[str, Any]:
|
|
117
|
+
return self.properties
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def from_dict(d: dict[str, Any]) -> Dimension:
|
|
121
|
+
dim_type: str = get_required(
|
|
122
|
+
d.get(DIM_TYPE_PROP), "cube_dimension", DIM_TYPE_PROP
|
|
123
|
+
)
|
|
124
|
+
if dim_type == DimensionType.SPATIAL:
|
|
125
|
+
axis: str = get_required(
|
|
126
|
+
d.get(DIM_AXIS_PROP), "cube_dimension", DIM_AXIS_PROP
|
|
127
|
+
)
|
|
128
|
+
if axis == "z":
|
|
129
|
+
return VerticalSpatialDimension(d)
|
|
130
|
+
else:
|
|
131
|
+
return HorizontalSpatialDimension(d)
|
|
132
|
+
elif dim_type == DimensionType.GEOMETRIES:
|
|
133
|
+
return VectorSpatialDimension(d)
|
|
134
|
+
elif dim_type == DimensionType.TEMPORAL:
|
|
135
|
+
# The v1.0.0 spec says that AdditionalDimensions can have
|
|
136
|
+
# type 'temporal', but it is unclear how to differentiate that
|
|
137
|
+
# from a temporal dimension. Just key off of type for now.
|
|
138
|
+
# See https://github.com/stac-extensions/datacube/issues/5
|
|
139
|
+
return TemporalDimension(d)
|
|
140
|
+
else:
|
|
141
|
+
return AdditionalDimension(d)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class SpatialDimension(Dimension):
|
|
145
|
+
@property
|
|
146
|
+
def extent(self) -> list[float] | None:
|
|
147
|
+
"""Extent (lower and upper bounds) of the dimension as two-dimensional array.
|
|
148
|
+
Open intervals with ``None`` are not allowed."""
|
|
149
|
+
return cast(
|
|
150
|
+
list[float],
|
|
151
|
+
get_required(
|
|
152
|
+
self.properties.get(DIM_EXTENT_PROP), "cube:dimension", DIM_EXTENT_PROP
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
@extent.setter
|
|
157
|
+
def extent(self, v: list[float] | None) -> None:
|
|
158
|
+
self.properties[DIM_EXTENT_PROP] = v
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def values(self) -> list[float] | None:
|
|
162
|
+
"""Optional set of all potential values."""
|
|
163
|
+
return self.properties.get(DIM_VALUES_PROP)
|
|
164
|
+
|
|
165
|
+
@values.setter
|
|
166
|
+
def values(self, v: list[float] | None) -> None:
|
|
167
|
+
if v is None:
|
|
168
|
+
self.properties.pop(DIM_VALUES_PROP, None)
|
|
169
|
+
else:
|
|
170
|
+
self.properties[DIM_VALUES_PROP] = v
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def step(self) -> float | None:
|
|
174
|
+
"""The space between the values. Use ``None`` for irregularly spaced steps."""
|
|
175
|
+
return self.properties.get(DIM_STEP_PROP)
|
|
176
|
+
|
|
177
|
+
@step.setter
|
|
178
|
+
def step(self, v: float | None) -> None:
|
|
179
|
+
self.properties[DIM_STEP_PROP] = v
|
|
180
|
+
|
|
181
|
+
def clear_step(self) -> None:
|
|
182
|
+
"""Setting step to None sets it to the null value,
|
|
183
|
+
which means irregularly spaced steps. Use clear_step
|
|
184
|
+
to remove it from the properties."""
|
|
185
|
+
self.properties.pop(DIM_STEP_PROP, None)
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def reference_system(self) -> str | float | dict[str, Any] | None:
|
|
189
|
+
"""The spatial reference system for the data, specified as `numerical EPSG code
|
|
190
|
+
<http://www.epsg-registry.org/>`__, `WKT2 (ISO 19162) string
|
|
191
|
+
<http://docs.opengeospatial.org/is/18-010r7/18-010r7.html>`__ or `PROJJSON
|
|
192
|
+
object <https://proj.org/specifications/projjson.html>`__.
|
|
193
|
+
Defaults to EPSG code 4326."""
|
|
194
|
+
return self.properties.get(DIM_REF_SYS_PROP)
|
|
195
|
+
|
|
196
|
+
@reference_system.setter
|
|
197
|
+
def reference_system(self, v: str | float | dict[str, Any] | None) -> None:
|
|
198
|
+
if v is None:
|
|
199
|
+
self.properties.pop(DIM_REF_SYS_PROP, None)
|
|
200
|
+
else:
|
|
201
|
+
self.properties[DIM_REF_SYS_PROP] = v
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class HorizontalSpatialDimension(SpatialDimension):
|
|
205
|
+
@property
|
|
206
|
+
def axis(self) -> HorizontalSpatialDimensionAxis:
|
|
207
|
+
"""Axis of the spatial dimension. Must be one of ``"x"`` or ``"y"``."""
|
|
208
|
+
return cast(
|
|
209
|
+
HorizontalSpatialDimensionAxis,
|
|
210
|
+
get_required(
|
|
211
|
+
self.properties.get(DIM_AXIS_PROP), "cube:dimension", DIM_AXIS_PROP
|
|
212
|
+
),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
@axis.setter
|
|
216
|
+
def axis(self, v: HorizontalSpatialDimensionAxis) -> None:
|
|
217
|
+
self.properties[DIM_AXIS_PROP] = v
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class VerticalSpatialDimension(SpatialDimension):
|
|
221
|
+
@property
|
|
222
|
+
def axis(self) -> VerticalSpatialDimensionAxis:
|
|
223
|
+
"""Axis of the spatial dimension. Must be ``"z"``."""
|
|
224
|
+
return cast(
|
|
225
|
+
VerticalSpatialDimensionAxis,
|
|
226
|
+
get_required(
|
|
227
|
+
self.properties.get(DIM_AXIS_PROP), "cube:dimension", DIM_AXIS_PROP
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
@axis.setter
|
|
232
|
+
def axis(self, v: VerticalSpatialDimensionAxis) -> None:
|
|
233
|
+
self.properties[DIM_AXIS_PROP] = v
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def extent(self) -> list[float] | None:
|
|
237
|
+
"""Extent (lower and upper bounds) of the dimension as two-dimensional array.
|
|
238
|
+
Open intervals with ``None`` are not allowed."""
|
|
239
|
+
return self.properties.get(DIM_EXTENT_PROP)
|
|
240
|
+
|
|
241
|
+
@extent.setter
|
|
242
|
+
def extent(self, v: list[float] | None) -> None:
|
|
243
|
+
if v is None:
|
|
244
|
+
self.properties.pop(DIM_EXTENT_PROP, None)
|
|
245
|
+
else:
|
|
246
|
+
self.properties[DIM_EXTENT_PROP] = v
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def unit(self) -> str | None:
|
|
250
|
+
"""The unit of measurement for the data, preferably compliant to `UDUNITS-2
|
|
251
|
+
<https://ncics.org/portfolio/other-resources/udunits2/>`__ units (singular)."""
|
|
252
|
+
return self.properties.get(DIM_UNIT_PROP)
|
|
253
|
+
|
|
254
|
+
@unit.setter
|
|
255
|
+
def unit(self, v: str | None) -> None:
|
|
256
|
+
if v is None:
|
|
257
|
+
self.properties.pop(DIM_UNIT_PROP, None)
|
|
258
|
+
else:
|
|
259
|
+
self.properties[DIM_UNIT_PROP] = v
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class VectorSpatialDimension(Dimension):
|
|
263
|
+
@property
|
|
264
|
+
def axes(self) -> list[str] | None:
|
|
265
|
+
"""Axes of the vector dimension as an ordered set of `x`, `y` and `z`."""
|
|
266
|
+
return self.properties.get("axes")
|
|
267
|
+
|
|
268
|
+
@axes.setter
|
|
269
|
+
def axes(self, v: list[str]) -> None:
|
|
270
|
+
if v is None:
|
|
271
|
+
self.properties.pop("axes", None)
|
|
272
|
+
else:
|
|
273
|
+
self.properties["axes"] = v
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def bbox(self) -> list[float]:
|
|
277
|
+
"""A single bounding box of the geometries as defined for STAC
|
|
278
|
+
Collections but not nested."""
|
|
279
|
+
return get_required(self.properties.get("bbox"), "cube:bbox", "bbox")
|
|
280
|
+
|
|
281
|
+
@bbox.setter
|
|
282
|
+
def bbox(self, v: list[float]) -> None:
|
|
283
|
+
self.properties["bbox"] = v
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def values(self) -> list[str] | None:
|
|
287
|
+
"""Optionally, a representation of the geometries. This could be a list
|
|
288
|
+
of WKT strings or other identifiers."""
|
|
289
|
+
return self.properties.get(DIM_VALUES_PROP)
|
|
290
|
+
|
|
291
|
+
@values.setter
|
|
292
|
+
def values(self, v: list[str] | None) -> None:
|
|
293
|
+
if v is None:
|
|
294
|
+
self.properties.pop(DIM_VALUES_PROP, None)
|
|
295
|
+
else:
|
|
296
|
+
self.properties[DIM_VALUES_PROP] = v
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def geometry_types(self) -> list[str] | None:
|
|
300
|
+
"""A set of geometry types. If not present, mixed geometry types must be
|
|
301
|
+
assumed."""
|
|
302
|
+
return self.properties.get("geometry_types")
|
|
303
|
+
|
|
304
|
+
@geometry_types.setter
|
|
305
|
+
def geometry_types(self, v: list[str] | None) -> None:
|
|
306
|
+
if v is None:
|
|
307
|
+
self.properties.pop("geometry_types", None)
|
|
308
|
+
else:
|
|
309
|
+
self.properties["geometry_types"] = v
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def reference_system(self) -> str | float | dict[str, Any] | None:
|
|
313
|
+
"""The reference system for the data."""
|
|
314
|
+
return self.properties.get(DIM_REF_SYS_PROP)
|
|
315
|
+
|
|
316
|
+
@reference_system.setter
|
|
317
|
+
def reference_system(self, v: str | float | dict[str, Any] | None) -> None:
|
|
318
|
+
if v is None:
|
|
319
|
+
self.properties.pop(DIM_REF_SYS_PROP, None)
|
|
320
|
+
else:
|
|
321
|
+
self.properties[DIM_REF_SYS_PROP] = v
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class TemporalDimension(Dimension):
|
|
325
|
+
@property
|
|
326
|
+
def extent(self) -> list[str | None] | None:
|
|
327
|
+
"""Extent (lower and upper bounds) of the dimension as two-dimensional array.
|
|
328
|
+
The dates and/or times must be strings compliant to `ISO 8601
|
|
329
|
+
<https://en.wikipedia.org/wiki/ISO_8601>`__. ``None`` is allowed for open date
|
|
330
|
+
ranges."""
|
|
331
|
+
return self.properties.get(DIM_EXTENT_PROP)
|
|
332
|
+
|
|
333
|
+
@extent.setter
|
|
334
|
+
def extent(self, v: list[str | None] | None) -> None:
|
|
335
|
+
if v is None:
|
|
336
|
+
self.properties.pop(DIM_EXTENT_PROP, None)
|
|
337
|
+
else:
|
|
338
|
+
self.properties[DIM_EXTENT_PROP] = v
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def values(self) -> list[str] | None:
|
|
342
|
+
"""If the dimension consists of set of specific values they can be listed here.
|
|
343
|
+
The dates and/or times must be strings compliant to `ISO 8601
|
|
344
|
+
<https://en.wikipedia.org/wiki/ISO_8601>`__."""
|
|
345
|
+
return self.properties.get(DIM_VALUES_PROP)
|
|
346
|
+
|
|
347
|
+
@values.setter
|
|
348
|
+
def values(self, v: list[str] | None) -> None:
|
|
349
|
+
if v is None:
|
|
350
|
+
self.properties.pop(DIM_VALUES_PROP, None)
|
|
351
|
+
else:
|
|
352
|
+
self.properties[DIM_VALUES_PROP] = v
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def step(self) -> str | None:
|
|
356
|
+
"""The space between the temporal instances as `ISO 8601 duration
|
|
357
|
+
<https://en.wikipedia.org/wiki/ISO_8601#Durations>`__, e.g. P1D. Use null for
|
|
358
|
+
irregularly spaced steps."""
|
|
359
|
+
return self.properties.get(DIM_STEP_PROP)
|
|
360
|
+
|
|
361
|
+
@step.setter
|
|
362
|
+
def step(self, v: str | None) -> None:
|
|
363
|
+
self.properties[DIM_STEP_PROP] = v
|
|
364
|
+
|
|
365
|
+
def clear_step(self) -> None:
|
|
366
|
+
"""Setting step to None sets it to the null value,
|
|
367
|
+
which means irregularly spaced steps. Use clear_step
|
|
368
|
+
to remove it from the properties."""
|
|
369
|
+
self.properties.pop(DIM_STEP_PROP, None)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class AdditionalDimension(Dimension):
|
|
373
|
+
@property
|
|
374
|
+
def extent(self) -> list[float | None] | None:
|
|
375
|
+
"""If the dimension consists of `ordinal
|
|
376
|
+
<https://en.wikipedia.org/wiki/Level_of_measurement#Ordinal_scale>`__ values,
|
|
377
|
+
the extent (lower and upper bounds) of the values as two-dimensional array. Use
|
|
378
|
+
null for open intervals."""
|
|
379
|
+
return self.properties.get(DIM_EXTENT_PROP)
|
|
380
|
+
|
|
381
|
+
@extent.setter
|
|
382
|
+
def extent(self, v: list[float | None] | None) -> None:
|
|
383
|
+
if v is None:
|
|
384
|
+
self.properties.pop(DIM_EXTENT_PROP, None)
|
|
385
|
+
else:
|
|
386
|
+
self.properties[DIM_EXTENT_PROP] = v
|
|
387
|
+
|
|
388
|
+
@property
|
|
389
|
+
def values(self) -> list[str] | list[float] | None:
|
|
390
|
+
"""A set of all potential values, especially useful for `nominal
|
|
391
|
+
<https://en.wikipedia.org/wiki/Level_of_measurement#Nominal_level>`__ values."""
|
|
392
|
+
return self.properties.get(DIM_VALUES_PROP)
|
|
393
|
+
|
|
394
|
+
@values.setter
|
|
395
|
+
def values(self, v: list[str] | list[float] | None) -> None:
|
|
396
|
+
if v is None:
|
|
397
|
+
self.properties.pop(DIM_VALUES_PROP, None)
|
|
398
|
+
else:
|
|
399
|
+
self.properties[DIM_VALUES_PROP] = v
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
def step(self) -> float | None:
|
|
403
|
+
"""If the dimension consists of `interval
|
|
404
|
+
<https://en.wikipedia.org/wiki/Level_of_measurement#Interval_scale>`__ values,
|
|
405
|
+
the space between the values. Use null for irregularly spaced steps."""
|
|
406
|
+
return self.properties.get(DIM_STEP_PROP)
|
|
407
|
+
|
|
408
|
+
@step.setter
|
|
409
|
+
def step(self, v: float | None) -> None:
|
|
410
|
+
self.properties[DIM_STEP_PROP] = v
|
|
411
|
+
|
|
412
|
+
def clear_step(self) -> None:
|
|
413
|
+
"""Setting step to None sets it to the null value,
|
|
414
|
+
which means irregularly spaced steps. Use clear_step
|
|
415
|
+
to remove it from the properties."""
|
|
416
|
+
self.properties.pop(DIM_STEP_PROP, None)
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def unit(self) -> str | None:
|
|
420
|
+
"""The unit of measurement for the data, preferably compliant to `UDUNITS-2
|
|
421
|
+
units <https://ncics.org/portfolio/other-resources/udunits2/>`__ (singular)."""
|
|
422
|
+
return self.properties.get(DIM_UNIT_PROP)
|
|
423
|
+
|
|
424
|
+
@unit.setter
|
|
425
|
+
def unit(self, v: str | None) -> None:
|
|
426
|
+
if v is None:
|
|
427
|
+
self.properties.pop(DIM_UNIT_PROP, None)
|
|
428
|
+
else:
|
|
429
|
+
self.properties[DIM_UNIT_PROP] = v
|
|
430
|
+
|
|
431
|
+
@property
|
|
432
|
+
def reference_system(self) -> str | float | dict[str, Any] | None:
|
|
433
|
+
"""The reference system for the data."""
|
|
434
|
+
return self.properties.get(DIM_REF_SYS_PROP)
|
|
435
|
+
|
|
436
|
+
@reference_system.setter
|
|
437
|
+
def reference_system(self, v: str | float | dict[str, Any] | None) -> None:
|
|
438
|
+
if v is None:
|
|
439
|
+
self.properties.pop(DIM_REF_SYS_PROP, None)
|
|
440
|
+
else:
|
|
441
|
+
self.properties[DIM_REF_SYS_PROP] = v
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
class VariableType(StringEnum):
|
|
445
|
+
"""Variable object types"""
|
|
446
|
+
|
|
447
|
+
DATA = "data"
|
|
448
|
+
AUXILIARY = "auxiliary"
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class Variable:
|
|
452
|
+
"""Object representing a variable in the datacube. The dimensions field lists
|
|
453
|
+
zero or more :stac-ext:`Datacube Dimension Object <datacube#dimension-object>`
|
|
454
|
+
instances. See the :stac-ext:`Datacube Variable Object
|
|
455
|
+
<datacube#variable-object>` docs for details.
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
properties: dict[str, Any]
|
|
459
|
+
|
|
460
|
+
def __init__(self, properties: dict[str, Any]) -> None:
|
|
461
|
+
self.properties = properties
|
|
462
|
+
|
|
463
|
+
@property
|
|
464
|
+
def dimensions(self) -> list[str]:
|
|
465
|
+
"""The dimensions of the variable. Should refer to keys in the
|
|
466
|
+
``cube:dimensions`` object or be an empty list if the variable has no
|
|
467
|
+
dimensions
|
|
468
|
+
"""
|
|
469
|
+
return get_required(
|
|
470
|
+
self.properties.get(VAR_DIMENSIONS_PROP),
|
|
471
|
+
"cube:variable",
|
|
472
|
+
VAR_DIMENSIONS_PROP,
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
@dimensions.setter
|
|
476
|
+
def dimensions(self, v: list[str]) -> None:
|
|
477
|
+
self.properties[VAR_DIMENSIONS_PROP] = v
|
|
478
|
+
|
|
479
|
+
@property
|
|
480
|
+
def var_type(self) -> VariableType | str:
|
|
481
|
+
"""Type of the variable, either ``data`` or ``auxiliary``"""
|
|
482
|
+
return cast(
|
|
483
|
+
str,
|
|
484
|
+
get_required(
|
|
485
|
+
self.properties.get(VAR_TYPE_PROP), "cube:variable", VAR_TYPE_PROP
|
|
486
|
+
),
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
@var_type.setter
|
|
490
|
+
def var_type(self, v: VariableType | str) -> None:
|
|
491
|
+
self.properties[VAR_TYPE_PROP] = v
|
|
492
|
+
|
|
493
|
+
@property
|
|
494
|
+
def description(self) -> str | None:
|
|
495
|
+
"""Detailed multi-line description to explain the variable. `CommonMark 0.29
|
|
496
|
+
<http://commonmark.org/>`__ syntax MAY be used for rich text representation."""
|
|
497
|
+
return self.properties.get(VAR_DESC_PROP)
|
|
498
|
+
|
|
499
|
+
@description.setter
|
|
500
|
+
def description(self, v: str | None) -> None:
|
|
501
|
+
if v is None:
|
|
502
|
+
self.properties.pop(VAR_DESC_PROP, None)
|
|
503
|
+
else:
|
|
504
|
+
self.properties[VAR_DESC_PROP] = v
|
|
505
|
+
|
|
506
|
+
@property
|
|
507
|
+
def extent(self) -> list[float | str | None]:
|
|
508
|
+
"""If the variable consists of `ordinal values
|
|
509
|
+
<https://en.wikipedia.org/wiki/Level_of_measurement#Ordinal_scale>`, the extent
|
|
510
|
+
(lower and upper bounds) of the values as two-dimensional array. Use ``None``
|
|
511
|
+
for open intervals"""
|
|
512
|
+
return get_required(
|
|
513
|
+
self.properties.get(VAR_EXTENT_PROP), "cube:variable", VAR_EXTENT_PROP
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
@extent.setter
|
|
517
|
+
def extent(self, v: list[float | str | None]) -> None:
|
|
518
|
+
self.properties[VAR_EXTENT_PROP] = v
|
|
519
|
+
|
|
520
|
+
@property
|
|
521
|
+
def values(self) -> list[float | str] | None:
|
|
522
|
+
"""A set of all potential values, especially useful for `nominal values
|
|
523
|
+
<https://en.wikipedia.org/wiki/Level_of_measurement#Nominal_level>`."""
|
|
524
|
+
return self.properties.get(VAR_VALUES_PROP)
|
|
525
|
+
|
|
526
|
+
@values.setter
|
|
527
|
+
def values(self, v: list[float | str] | None) -> None:
|
|
528
|
+
if v is None:
|
|
529
|
+
self.properties.pop(VAR_VALUES_PROP)
|
|
530
|
+
else:
|
|
531
|
+
self.properties[VAR_VALUES_PROP] = v
|
|
532
|
+
|
|
533
|
+
@property
|
|
534
|
+
def unit(self) -> str | None:
|
|
535
|
+
"""The unit of measurement for the data, preferably compliant to `UDUNITS-2
|
|
536
|
+
<https://ncics.org/portfolio/other-resources/udunits2/>` units (singular)"""
|
|
537
|
+
return self.properties.get(VAR_UNIT_PROP)
|
|
538
|
+
|
|
539
|
+
@unit.setter
|
|
540
|
+
def unit(self, v: str | None) -> None:
|
|
541
|
+
if v is None:
|
|
542
|
+
self.properties.pop(VAR_UNIT_PROP)
|
|
543
|
+
else:
|
|
544
|
+
self.properties[VAR_UNIT_PROP] = v
|
|
545
|
+
|
|
546
|
+
@staticmethod
|
|
547
|
+
def from_dict(d: dict[str, Any]) -> Variable:
|
|
548
|
+
return Variable(d)
|
|
549
|
+
|
|
550
|
+
def to_dict(self) -> dict[str, Any]:
|
|
551
|
+
return self.properties
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
class DatacubeExtension(
|
|
555
|
+
Generic[T],
|
|
556
|
+
PropertiesExtension,
|
|
557
|
+
ExtensionManagementMixin[pystac.Item | pystac.Collection],
|
|
558
|
+
):
|
|
559
|
+
"""An abstract class that can be used to extend the properties of a
|
|
560
|
+
:class:`~pystac.Collection`, :class:`~pystac.Item`, or :class:`~pystac.Asset` with
|
|
561
|
+
properties from the :stac-ext:`Datacube Extension <datacube>`. This class is
|
|
562
|
+
generic over the type of STAC Object to be extended (e.g. :class:`~pystac.Item`,
|
|
563
|
+
:class:`~pystac.Asset`).
|
|
564
|
+
|
|
565
|
+
To create a concrete instance of :class:`DatacubeExtension`, use the
|
|
566
|
+
:meth:`DatacubeExtension.ext` method. For example:
|
|
567
|
+
|
|
568
|
+
.. code-block:: python
|
|
569
|
+
|
|
570
|
+
>>> item: pystac.Item = ...
|
|
571
|
+
>>> dc_ext = DatacubeExtension.ext(item)
|
|
572
|
+
"""
|
|
573
|
+
|
|
574
|
+
name: Literal["cube"] = "cube"
|
|
575
|
+
|
|
576
|
+
def apply(
|
|
577
|
+
self,
|
|
578
|
+
dimensions: dict[str, Dimension],
|
|
579
|
+
variables: dict[str, Variable] | None = None,
|
|
580
|
+
) -> None:
|
|
581
|
+
"""Applies Datacube Extension properties to the extended
|
|
582
|
+
:class:`~pystac.Collection`, :class:`~pystac.Item` or :class:`~pystac.Asset`.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
dimensions : Dictionary mapping dimension name to :class:`Dimension`
|
|
586
|
+
objects.
|
|
587
|
+
variables : Dictionary mapping variable name to a :class:`Variable`
|
|
588
|
+
object.
|
|
589
|
+
"""
|
|
590
|
+
self.dimensions = dimensions
|
|
591
|
+
self.variables = variables
|
|
592
|
+
|
|
593
|
+
@property
|
|
594
|
+
def dimensions(self) -> dict[str, Dimension]:
|
|
595
|
+
"""A dictionary where each key is the name of a dimension and each
|
|
596
|
+
value is a :class:`~Dimension` object.
|
|
597
|
+
"""
|
|
598
|
+
result = get_required(
|
|
599
|
+
self._get_property(DIMENSIONS_PROP, dict[str, Any]), self, DIMENSIONS_PROP
|
|
600
|
+
)
|
|
601
|
+
return {k: Dimension.from_dict(v) for k, v in result.items()}
|
|
602
|
+
|
|
603
|
+
@dimensions.setter
|
|
604
|
+
def dimensions(self, v: dict[str, Dimension]) -> None:
|
|
605
|
+
self._set_property(DIMENSIONS_PROP, {k: dim.to_dict() for k, dim in v.items()})
|
|
606
|
+
|
|
607
|
+
@property
|
|
608
|
+
def variables(self) -> dict[str, Variable] | None:
|
|
609
|
+
"""A dictionary where each key is the name of a variable and each
|
|
610
|
+
value is a :class:`~Variable` object.
|
|
611
|
+
"""
|
|
612
|
+
result = self._get_property(VARIABLES_PROP, dict[str, Any])
|
|
613
|
+
|
|
614
|
+
if result is None:
|
|
615
|
+
return None
|
|
616
|
+
return {k: Variable.from_dict(v) for k, v in result.items()}
|
|
617
|
+
|
|
618
|
+
@variables.setter
|
|
619
|
+
def variables(self, v: dict[str, Variable] | None) -> None:
|
|
620
|
+
self._set_property(
|
|
621
|
+
VARIABLES_PROP,
|
|
622
|
+
map_opt(
|
|
623
|
+
lambda variables: {k: var.to_dict() for k, var in variables.items()}, v
|
|
624
|
+
),
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
@classmethod
|
|
628
|
+
def get_schema_uri(cls) -> str:
|
|
629
|
+
return SCHEMA_URI
|
|
630
|
+
|
|
631
|
+
@classmethod
|
|
632
|
+
def ext(cls, obj: T, add_if_missing: bool = False) -> DatacubeExtension[T]:
|
|
633
|
+
"""Extends the given STAC Object with properties from the :stac-ext:`Datacube
|
|
634
|
+
Extension <datacube>`.
|
|
635
|
+
|
|
636
|
+
This extension can be applied to instances of :class:`~pystac.Collection`,
|
|
637
|
+
:class:`~pystac.Item` or :class:`~pystac.Asset`.
|
|
638
|
+
|
|
639
|
+
Raises:
|
|
640
|
+
|
|
641
|
+
pystac.ExtensionTypeError : If an invalid object type is passed.
|
|
642
|
+
"""
|
|
643
|
+
if isinstance(obj, pystac.Collection):
|
|
644
|
+
cls.ensure_has_extension(obj, add_if_missing)
|
|
645
|
+
return cast(DatacubeExtension[T], CollectionDatacubeExtension(obj))
|
|
646
|
+
if isinstance(obj, pystac.Item):
|
|
647
|
+
cls.ensure_has_extension(obj, add_if_missing)
|
|
648
|
+
return cast(DatacubeExtension[T], ItemDatacubeExtension(obj))
|
|
649
|
+
elif isinstance(obj, pystac.Asset):
|
|
650
|
+
cls.ensure_owner_has_extension(obj, add_if_missing)
|
|
651
|
+
return cast(DatacubeExtension[T], AssetDatacubeExtension(obj))
|
|
652
|
+
elif isinstance(obj, pystac.ItemAssetDefinition):
|
|
653
|
+
cls.ensure_owner_has_extension(obj, add_if_missing)
|
|
654
|
+
return cast(DatacubeExtension[T], ItemAssetsDatacubeExtension(obj))
|
|
655
|
+
else:
|
|
656
|
+
raise pystac.ExtensionTypeError(cls._ext_error_message(obj))
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
class CollectionDatacubeExtension(DatacubeExtension[pystac.Collection]):
|
|
660
|
+
"""A concrete implementation of :class:`DatacubeExtension` on an
|
|
661
|
+
:class:`~pystac.Collection` that extends the properties of the Item to include
|
|
662
|
+
properties defined in the :stac-ext:`Datacube Extension <datacube>`.
|
|
663
|
+
|
|
664
|
+
This class should generally not be instantiated directly. Instead, call
|
|
665
|
+
:meth:`DatacubeExtension.ext` on an :class:`~pystac.Collection` to extend it.
|
|
666
|
+
"""
|
|
667
|
+
|
|
668
|
+
collection: pystac.Collection
|
|
669
|
+
properties: dict[str, Any]
|
|
670
|
+
|
|
671
|
+
def __init__(self, collection: pystac.Collection):
|
|
672
|
+
self.collection = collection
|
|
673
|
+
self.properties = collection.extra_fields
|
|
674
|
+
|
|
675
|
+
def __repr__(self) -> str:
|
|
676
|
+
return f"<CollectionDatacubeExtension Item id={self.collection.id}>"
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
class ItemDatacubeExtension(DatacubeExtension[pystac.Item]):
|
|
680
|
+
"""A concrete implementation of :class:`DatacubeExtension` on an
|
|
681
|
+
:class:`~pystac.Item` that extends the properties of the Item to include properties
|
|
682
|
+
defined in the :stac-ext:`Datacube Extension <datacube>`.
|
|
683
|
+
|
|
684
|
+
This class should generally not be instantiated directly. Instead, call
|
|
685
|
+
:meth:`DatacubeExtension.ext` on an :class:`~pystac.Item` to extend it.
|
|
686
|
+
"""
|
|
687
|
+
|
|
688
|
+
item: pystac.Item
|
|
689
|
+
properties: dict[str, Any]
|
|
690
|
+
|
|
691
|
+
def __init__(self, item: pystac.Item):
|
|
692
|
+
self.item = item
|
|
693
|
+
self.properties = item.properties
|
|
694
|
+
|
|
695
|
+
def __repr__(self) -> str:
|
|
696
|
+
return f"<ItemDatacubeExtension Item id={self.item.id}>"
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
class AssetDatacubeExtension(DatacubeExtension[pystac.Asset]):
|
|
700
|
+
"""A concrete implementation of :class:`DatacubeExtension` on an
|
|
701
|
+
:class:`~pystac.Asset` that extends the Asset fields to include properties defined
|
|
702
|
+
in the :stac-ext:`Datacube Extension <datacube>`.
|
|
703
|
+
|
|
704
|
+
This class should generally not be instantiated directly. Instead, call
|
|
705
|
+
:meth:`DatacubeExtension.ext` on an :class:`~pystac.Asset` to extend it.
|
|
706
|
+
"""
|
|
707
|
+
|
|
708
|
+
asset_href: str
|
|
709
|
+
properties: dict[str, Any]
|
|
710
|
+
additional_read_properties: Iterable[dict[str, Any]] | None
|
|
711
|
+
|
|
712
|
+
def __init__(self, asset: pystac.Asset):
|
|
713
|
+
self.asset_href = asset.href
|
|
714
|
+
self.properties = asset.extra_fields
|
|
715
|
+
if asset.owner and isinstance(asset.owner, pystac.Item):
|
|
716
|
+
self.additional_read_properties = [asset.owner.properties]
|
|
717
|
+
else:
|
|
718
|
+
self.additional_read_properties = None
|
|
719
|
+
|
|
720
|
+
def __repr__(self) -> str:
|
|
721
|
+
return f"<AssetDatacubeExtension Item id={self.asset_href}>"
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
class ItemAssetsDatacubeExtension(DatacubeExtension[pystac.ItemAssetDefinition]):
|
|
725
|
+
properties: dict[str, Any]
|
|
726
|
+
asset_defn: pystac.ItemAssetDefinition
|
|
727
|
+
|
|
728
|
+
def __init__(self, item_asset: pystac.ItemAssetDefinition):
|
|
729
|
+
self.asset_defn = item_asset
|
|
730
|
+
self.properties = item_asset.properties
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
class DatacubeExtensionHooks(ExtensionHooks):
|
|
734
|
+
schema_uri: str = SCHEMA_URI
|
|
735
|
+
prev_extension_ids = {
|
|
736
|
+
"datacube",
|
|
737
|
+
"https://stac-extensions.github.io/datacube/v1.0.0/schema.json",
|
|
738
|
+
"https://stac-extensions.github.io/datacube/v2.0.0/schema.json",
|
|
739
|
+
"https://stac-extensions.github.io/datacube/v2.1.0/schema.json",
|
|
740
|
+
}
|
|
741
|
+
stac_object_types = {
|
|
742
|
+
pystac.STACObjectType.COLLECTION,
|
|
743
|
+
pystac.STACObjectType.ITEM,
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
DATACUBE_EXTENSION_HOOKS: ExtensionHooks = DatacubeExtensionHooks()
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pystac-ext-datacube
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Datacube extension for PySTAC
|
|
5
|
+
Project-URL: Documentation, https://pystac.readthedocs.io
|
|
6
|
+
Project-URL: Repository, https://github.com/stac-utils/pystac
|
|
7
|
+
Project-URL: Issues, https://github.com/stac-utils/pystac/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Discussions, https://github.com/radiantearth/stac-spec/discussions/categories/stac-software
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
Keywords: STAC,catalog,datacube,imagery,pystac,raster
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: pystac-core
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# pystac-ext-datacube
|
|
26
|
+
|
|
27
|
+
[PySTAC](https://pypi.org/project/pystac/) extension package for the [Datacube Extension](https://github.com/stac-extensions/datacube).
|
|
28
|
+
This extension describes datasets that are organized as multi-dimensional datacubes, including dimension types, extents, values, and reference systems.
|
|
29
|
+
|
|
30
|
+
## Supported versions
|
|
31
|
+
|
|
32
|
+
- [v2.2.0](https://stac-extensions.github.io/datacube/v2.2.0/schema.json)
|
|
33
|
+
|
|
34
|
+
## Versioning
|
|
35
|
+
|
|
36
|
+
This package's version corresponds to the version of the extension specification it targets.
|
|
37
|
+
When we release updates to the package code without changing the target extension version, we use [post releases](https://packaging.python.org/en/latest/discussions/versioning/#post-releases), e.g. `2.2.0.post1`.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
pystac/extensions/datacube.py,sha256=hqelwEQvCjCPLgM2G8umzMQ8l8sDPoykhhAviTiY2Tg,26505
|
|
2
|
+
pystac/extensions/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
pystac_ext_datacube-2.2.0.dist-info/METADATA,sha256=mcI00tN450rpSUzENQFlMkUiXrbO27vjvIVvtqBs1sQ,1828
|
|
4
|
+
pystac_ext_datacube-2.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
5
|
+
pystac_ext_datacube-2.2.0.dist-info/RECORD,,
|