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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any