disscube 0.4.0__tar.gz

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.
Files changed (78) hide show
  1. disscube-0.4.0/LICENSE +21 -0
  2. disscube-0.4.0/MANIFEST.in +3 -0
  3. disscube-0.4.0/PKG-INFO +368 -0
  4. disscube-0.4.0/README.md +310 -0
  5. disscube-0.4.0/disscube/__init__.py +19 -0
  6. disscube-0.4.0/disscube/catalog/__init__.py +5 -0
  7. disscube-0.4.0/disscube/catalog/json_store.py +94 -0
  8. disscube-0.4.0/disscube/catalog/protocol.py +22 -0
  9. disscube-0.4.0/disscube/catalog/sqlite_store.py +143 -0
  10. disscube-0.4.0/disscube/cli.py +144 -0
  11. disscube-0.4.0/disscube/client.py +521 -0
  12. disscube-0.4.0/disscube/data/bdc_grids/BDC_LG_V2.zip +0 -0
  13. disscube-0.4.0/disscube/data/bdc_grids/BDC_MD_V2.zip +0 -0
  14. disscube-0.4.0/disscube/data/bdc_grids/BDC_SM_V2.zip +0 -0
  15. disscube-0.4.0/disscube/data/bdc_grids/README.md +51 -0
  16. disscube-0.4.0/disscube/export.py +168 -0
  17. disscube-0.4.0/disscube/models/__init__.py +28 -0
  18. disscube-0.4.0/disscube/models/derivation.py +186 -0
  19. disscube-0.4.0/disscube/models/grid.py +240 -0
  20. disscube-0.4.0/disscube/models/variable.py +129 -0
  21. disscube-0.4.0/disscube/operators/__init__.py +20 -0
  22. disscube-0.4.0/disscube/operators/base.py +115 -0
  23. disscube-0.4.0/disscube/operators/proximity.py +170 -0
  24. disscube-0.4.0/disscube/operators/zonal.py +522 -0
  25. disscube-0.4.0/disscube/pipeline/__init__.py +41 -0
  26. disscube-0.4.0/disscube/pipeline/aggregator.py +152 -0
  27. disscube-0.4.0/disscube/pipeline/aligner.py +353 -0
  28. disscube-0.4.0/disscube/pipeline/context.py +24 -0
  29. disscube-0.4.0/disscube/pipeline/normalizer.py +59 -0
  30. disscube-0.4.0/disscube/pipeline/runner.py +693 -0
  31. disscube-0.4.0/disscube/pipeline/schema.py +212 -0
  32. disscube-0.4.0/disscube/pipeline/writer.py +144 -0
  33. disscube-0.4.0/disscube/sources/__init__.py +43 -0
  34. disscube-0.4.0/disscube/sources/_categorical.py +89 -0
  35. disscube-0.4.0/disscube/sources/_raster.py +303 -0
  36. disscube-0.4.0/disscube/sources/bdc.py +286 -0
  37. disscube-0.4.0/disscube/sources/classified.py +80 -0
  38. disscube-0.4.0/disscube/sources/mapbiomas.py +162 -0
  39. disscube-0.4.0/disscube/sources/prodes.py +254 -0
  40. disscube-0.4.0/disscube/storage.py +32 -0
  41. disscube-0.4.0/disscube/utils.py +150 -0
  42. disscube-0.4.0/disscube.egg-info/PKG-INFO +368 -0
  43. disscube-0.4.0/disscube.egg-info/SOURCES.txt +76 -0
  44. disscube-0.4.0/disscube.egg-info/dependency_links.txt +1 -0
  45. disscube-0.4.0/disscube.egg-info/entry_points.txt +2 -0
  46. disscube-0.4.0/disscube.egg-info/requires.txt +40 -0
  47. disscube-0.4.0/disscube.egg-info/top_level.txt +1 -0
  48. disscube-0.4.0/pyproject.toml +144 -0
  49. disscube-0.4.0/setup.cfg +4 -0
  50. disscube-0.4.0/tests/test_aggregator.py +129 -0
  51. disscube-0.4.0/tests/test_aligner.py +111 -0
  52. disscube-0.4.0/tests/test_aligner_resampling.py +246 -0
  53. disscube-0.4.0/tests/test_anchoring.py +76 -0
  54. disscube-0.4.0/tests/test_bdc_bundled_grids.py +105 -0
  55. disscube-0.4.0/tests/test_bdc_master_grids.py +195 -0
  56. disscube-0.4.0/tests/test_classified.py +78 -0
  57. disscube-0.4.0/tests/test_derivation.py +173 -0
  58. disscube-0.4.0/tests/test_distance_operator.py +73 -0
  59. disscube-0.4.0/tests/test_error_fallbacks.py +70 -0
  60. disscube-0.4.0/tests/test_examples.py +31 -0
  61. disscube-0.4.0/tests/test_export.py +218 -0
  62. disscube-0.4.0/tests/test_fetch.py +111 -0
  63. disscube-0.4.0/tests/test_geographic_grid.py +64 -0
  64. disscube-0.4.0/tests/test_geometry.py +42 -0
  65. disscube-0.4.0/tests/test_grid_relations.py +113 -0
  66. disscube-0.4.0/tests/test_mapbiomas.py +94 -0
  67. disscube-0.4.0/tests/test_models.py +52 -0
  68. disscube-0.4.0/tests/test_mutation.py +66 -0
  69. disscube-0.4.0/tests/test_nodata.py +104 -0
  70. disscube-0.4.0/tests/test_percentage_purity.py +196 -0
  71. disscube-0.4.0/tests/test_pipeline_file.py +375 -0
  72. disscube-0.4.0/tests/test_pipeline_options.py +407 -0
  73. disscube-0.4.0/tests/test_prodes.py +158 -0
  74. disscube-0.4.0/tests/test_roundtrip.py +213 -0
  75. disscube-0.4.0/tests/test_source_checksum.py +106 -0
  76. disscube-0.4.0/tests/test_sources.py +433 -0
  77. disscube-0.4.0/tests/test_spec_hash.py +113 -0
  78. disscube-0.4.0/tests/test_temporal.py +211 -0
disscube-0.4.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sérgio Costa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include LICENSE
2
+ include README.md
3
+ recursive-include disscube/data *
@@ -0,0 +1,368 @@
1
+ Metadata-Version: 2.4
2
+ Name: disscube
3
+ Version: 0.4.0
4
+ Summary: Declarative spatial data cubes: describe sources, grid and derived variables in TOML; get a cataloged, reproducible cube.
5
+ Author: Sérgio Souza Costa
6
+ Maintainer: Sérgio Souza Costa
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/DisSModel/disscube
9
+ Project-URL: Repository, https://github.com/DisSModel/disscube
10
+ Project-URL: Documentation, https://dissmodel.github.io/disscube/
11
+ Project-URL: Issues, https://github.com/DisSModel/disscube/issues
12
+ Project-URL: Changelog, https://github.com/DisSModel/disscube/blob/main/CHANGELOG.md
13
+ Keywords: spatial data cube,geospatial,land use and land cover change,brazil data cube,reproducibility
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Topic :: Scientific/Engineering :: GIS
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: pydantic>=2.0
24
+ Requires-Dist: xarray
25
+ Requires-Dist: zarr
26
+ Requires-Dist: rasterio
27
+ Requires-Dist: geopandas
28
+ Requires-Dist: shapely
29
+ Requires-Dist: scipy
30
+ Requires-Dist: numpy
31
+ Requires-Dist: pandas
32
+ Requires-Dist: pyproj
33
+ Requires-Dist: fsspec
34
+ Requires-Dist: toml
35
+ Requires-Dist: rioxarray
36
+ Requires-Dist: affine
37
+ Requires-Dist: pooch>=1.8.0
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest; extra == "dev"
40
+ Requires-Dist: pytest-cov; extra == "dev"
41
+ Requires-Dist: mypy>=1.13; extra == "dev"
42
+ Requires-Dist: ruff<0.17,>=0.16; extra == "dev"
43
+ Requires-Dist: ipykernel; extra == "dev"
44
+ Provides-Extra: bdc
45
+ Requires-Dist: fiona; extra == "bdc"
46
+ Requires-Dist: pystac-client; extra == "bdc"
47
+ Provides-Extra: s3
48
+ Requires-Dist: s3fs; extra == "s3"
49
+ Provides-Extra: netcdf
50
+ Requires-Dist: h5netcdf; extra == "netcdf"
51
+ Requires-Dist: h5py; extra == "netcdf"
52
+ Provides-Extra: dissmodel
53
+ Requires-Dist: dissmodel<0.7.0,>=0.6.0; extra == "dissmodel"
54
+ Provides-Extra: docs
55
+ Requires-Dist: mkdocs<2,>=1.6; extra == "docs"
56
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
57
+ Dynamic: license-file
58
+
59
+ # DisSCube
60
+
61
+ [![CI](https://github.com/DisSModel/disscube/actions/workflows/ci.yml/badge.svg)](https://github.com/DisSModel/disscube/actions/workflows/ci.yml)
62
+ [![Docs](https://github.com/DisSModel/disscube/actions/workflows/docs.yml/badge.svg)](https://dissmodel.github.io/disscube/)
63
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
64
+
65
+ > **Status: Alpha — stable APIs for the core pipeline; declarative models still evolving.**
66
+
67
+ DisSCube is the spatial data cube engine of the **DisSModel** ecosystem. It converts raw geospatial sources (rasters, vectors) into derived variables aligned to LUCC (Land Use and Cover Change) modeling grids, ready for Cellular Automata models and spatio-temporal analysis.
68
+
69
+ 📖 **Documentation:** <https://dissmodel.github.io/disscube/>
70
+
71
+ ## Core concept
72
+
73
+ ```
74
+ SpatialSource → Derivation → Variable → DerivedVariable (Zarr)
75
+ ```
76
+
77
+ A **source** (`SpatialSource`) goes through a **derivation** (`SpatialDerivation` or `Derivation`) that applies an **operator** on a **grid** (`GridSpec`), producing a **derived variable** registered in the SQLite catalog and stored in Zarr.
78
+
79
+ ## Installation
80
+
81
+ ```bash
82
+ git clone https://github.com/DisSModel/disscube.git
83
+ cd disscube
84
+ python -m venv .venv && source .venv/bin/activate
85
+ pip install -e .
86
+ ```
87
+
88
+ ## Basic usage
89
+
90
+ ### 1. Initialize the catalog and register a grid
91
+
92
+ ```python
93
+ from disscube.client import CubeClient
94
+ from disscube.utils.grids import register_local_grid
95
+
96
+ cube = CubeClient(catalog="catalog.db", store="./data/")
97
+
98
+ grid = register_local_grid(
99
+ cube,
100
+ name="AC",
101
+ bbox_geo=(-73.99, -11.15, -66.62, -7.11),
102
+ resolution=5_000.0,
103
+ )
104
+ ```
105
+
106
+ ### 2. Register a source
107
+
108
+ ```python
109
+ from disscube.models import SpatialSource
110
+
111
+ cube.register_spatial_source(SpatialSource(
112
+ id="mapbiomas_2020",
113
+ name="MapBiomas Acre 2020",
114
+ format="raster",
115
+ asset_url="data/raw/mapbiomas_2020.tif",
116
+ crs="EPSG:4326",
117
+ time=2020,
118
+ ))
119
+ ```
120
+
121
+ ### 3. Derive — declarative mode (recommended)
122
+
123
+ ```python
124
+ from disscube.derivation import Derivation
125
+
126
+ d = Derivation(
127
+ target="forest_pct",
128
+ source_id="mapbiomas_2020",
129
+ operator="percentage",
130
+ class_code=3,
131
+ role="driver",
132
+ valid_from="2020",
133
+ valid_until="2020",
134
+ )
135
+
136
+ cube.derive_declarative(d, grid_id="AC/5km")
137
+ ```
138
+
139
+ ### 4. Derive — direct mode
140
+
141
+ ```python
142
+ from disscube.models import SpatialDerivation, Variable
143
+
144
+ cube.derive(SpatialDerivation(
145
+ source_id="mapbiomas_2020",
146
+ grid_id="AC/5km",
147
+ role="driver",
148
+ variables=[Variable(name="forest_pct", operator="percentage", class_code=3)],
149
+ valid_from="2020",
150
+ valid_until="2020",
151
+ ))
152
+ ```
153
+
154
+ ### 5. Load the result
155
+
156
+ ```python
157
+ da = cube.load("forest_pct", grid_id="AC/5km")
158
+ print(da.shape) # (rows, cols)
159
+ ```
160
+
161
+ ### 6. Get the cube out
162
+
163
+ ```python
164
+ ds = cube.to_dataset(["forest_pct", "dist_roads"], grid_id="AC/5km", period=("2015", "2020"))
165
+ # xarray.Dataset: (y, x) static and (time, y, x) temporal variables, CRS and transform via ds.rio
166
+
167
+ cube.export_geotiff(["forest_pct"], "forest.tif", grid_id="AC/5km") # one band per variable and year
168
+ cube.export_netcdf(["forest_pct"], "cube.nc", grid_id="AC/5km") # CF-1.8; pip install "disscube[netcdf]"
169
+ ```
170
+
171
+ Exports carry their provenance: each GeoTIFF band and netCDF variable records the
172
+ `spec_hash`, the `content_hash` of the stored data and the `source_checksum` of the
173
+ input it came from (`cube.provenance("forest_pct")` lists them per year).
174
+
175
+ DisSCube does not need DisSModel. To hand a cube to a DisSModel model, install
176
+ `disscube[dissmodel]` and use `cube.to_raster_backend(...)`, which returns a `RasterBackend`.
177
+
178
+ ## Pipeline files (TOML)
179
+
180
+ A whole data preparation — grid, sources, derived variables — can be declared
181
+ in one TOML file, the counterpart of a TerraME `fill` script, and run from the
182
+ command line:
183
+
184
+ ```bash
185
+ disscube validate examples/pipelines/quickstart.toml # no downloads
186
+ disscube run examples/pipelines/quickstart.toml --workspace outputs/quickstart
187
+ ```
188
+
189
+ ```toml
190
+ schema = 1
191
+ name = "Quickstart"
192
+
193
+ [grid]
194
+ name = "demo/300m"
195
+ crs = "EPSG:31983"
196
+ bbox = [570000.0, 9708000.0, 582000.0, 9720000.0]
197
+ resolution = 300
198
+
199
+ [[source]]
200
+ id = "landuse"
201
+ type = "file"
202
+ path = "../data/quickstart/landuse.tif"
203
+ crs = "EPSG:31983"
204
+
205
+ [[derive]]
206
+ target = "forest_pct"
207
+ source = "landuse"
208
+ operator = "percentage"
209
+ class_code = 3
210
+ ```
211
+
212
+ See [`docs/guides/pipeline_files.md`](docs/guides/pipeline_files.md) and
213
+ [`examples/pipelines/`](examples/pipelines/).
214
+
215
+ ## Examples
216
+
217
+ [`examples/`](examples/) has runnable, self-contained examples that run offline in seconds:
218
+
219
+ - **01–03, synthetic data** — a quickstart with raster operators, vector drivers, and time series handed off to DisSModel.
220
+ - **quickstart.toml** — declarative pipeline equivalent to example 01.
221
+
222
+ ```bash
223
+ python examples/01_quickstart.py
224
+ disscube run examples/pipelines/quickstart.toml
225
+ ```
226
+
227
+ Real data workflows, TerraME parity benchmarks, and large-scale case studies are maintained in
228
+ [**LambdaGeo/disscube-recipes**](https://github.com/LambdaGeo/disscube-recipes) (`cases/terrame_fill`, `cases/ilha_maranhao`, `cases/prodes_br163`, `cases/luccme_br`).
229
+
230
+ See [`examples/README.md`](examples/README.md) for the full list, and
231
+ [`docs/terrame_fill_correspondence.md`](docs/terrame_fill_correspondence.md)
232
+ for how DisSCube's operators relate to TerraME's *Fill*.
233
+
234
+ ## Available operators
235
+
236
+ | Operator | Type | Resampling | Requires `class_code` |
237
+ |---|---|---|---|
238
+ | `mean` | zonal | average | no |
239
+ | `sum` | zonal | sum | no |
240
+ | `std` | zonal | nearest | no |
241
+ | `min` | zonal | min | no |
242
+ | `max` | zonal | max | no |
243
+ | `majority` | zonal | nearest¹ | no |
244
+ | `minority` | zonal | nearest¹ | no |
245
+ | `percentage` | zonal | nearest¹ | **yes** |
246
+ | `attribute` | zonal | nearest | no |
247
+ | `presence` | zonal | nearest | no |
248
+ | `distance` | proximity (exact, cell centre → nearest feature, source not clipped) | — | no |
249
+ | `min_distance` | proximity (raster approximation, features inside the grid) | nearest | no |
250
+ | `count` | proximity | nearest | no |
251
+ | `area` | polygons (exact share of the cell covered) | — | no |
252
+
253
+ `distance` takes `params = {crs = …}` to measure in another CRS (metres on a
254
+ geographic grid); the ¹ operators take `params = {subcells = n}` to cap the
255
+ fine pixels per cell. Any derivation takes `fill = "nearest"`.
256
+
257
+ > ¹ These use `needs_fine_alignment=True`: GridAligner resamples with `nearest` at high resolution; the actual reduction (per-window counting) is done by the operator.
258
+
259
+ ## Pipeline
260
+
261
+ ```
262
+ SpatialSource
263
+ │
264
+ ▼
265
+ Normalizer — validates / loads a GeoDataFrame (vector) or opens the raster
266
+ │
267
+ ▼
268
+ GridAligner — reprojects per variable with the operator's Resampling
269
+ │
270
+ ▼
271
+ Aggregator — delegates to operator.compute() → one xr.DataArray per variable
272
+ │
273
+ ▼
274
+ VariableWriter — writes Zarr + registers the DerivedVariable in the catalog
275
+ ```
276
+
277
+ ## Storage layout
278
+
279
+ ```
280
+ data/derived/{grid_id}/{partition}/{spec_hash}/{variable_name}.zarr
281
+ ```
282
+
283
+ - `partition` = `tile_id`, or `global` for untiled derivations.
284
+ - `spec_hash` = SHA-256 of the derivation (source + grid + variables + time window, plus the source's `checksum` when it has one — replacing a source file and registering its new checksum recomputes instead of returning a stale product).
285
+
286
+ ## Project structure
287
+
288
+ ```
289
+ disscube/
290
+ ├── client.py CubeClient — public entry point
291
+ ├── models/ GridSpec, SpatialSource, SpatialDerivation, Variable, Derivation…
292
+ ├── operators/ Operators as classes (self-registered via __init_subclass__)
293
+ │ ├── base.py Operator ABC + OPERATOR_REGISTRY
294
+ │ ├── zonal.py mean, sum, majority, percentage, attribute, presence…
295
+ │ └── proximity.py distance, min_distance, count
296
+ ├── pipeline/ Pipeline execution & planning (schema, runner) + internal stages
297
+ ├── catalog/ CatalogStore (Protocol) + SQLite and JSON implementations
298
+ ├── storage.py AssetStore (fsspec — local and S3)
299
+ ├── cli.py `disscube validate` / `disscube run` / `disscube export`
300
+ ├── sources/ Adapters that bring external data in as SpatialSources,
301
+ │ │ each with a checksum and a provenance.json sidecar
302
+ │ ├── _raster.py Window2D, windowed reads, composites, mosaics, register_raster
303
+ │ ├── bdc.py Brazil Data Cube cubes via STAC (`bdc` extra)
304
+ │ ├── _categorical.py legends (.qml/.json/.csv) and reclassification
305
+ │ ├── mapbiomas.py MapBiomas annual land-cover maps (Collection 11, 10 m series)
306
+ │ ├── prodes.py PRODES deforestation (download + cache, legend from the .qml)
307
+ │ └── classified.py any classified map with its legend, e.g. from SITS
308
+ └── utils.py Checksums (sha256_file) and BDC tile importer (import_bdc_grids)
309
+ ```
310
+
311
+ ## Adding a new operator
312
+
313
+ Create a subclass of `Operator` in any module imported at startup:
314
+
315
+ ```python
316
+ from rasterio.warp import Resampling
317
+ from disscube.operators.base import Operator
318
+
319
+ class WeightedMeanOperator(Operator):
320
+ name = "weighted_mean"
321
+ _resampling = Resampling.average
322
+
323
+ def compute(self, data, var, grid):
324
+ # data is an xr.DataArray (raster) or a GeoDataFrame (vector)
325
+ ...
326
+ ```
327
+
328
+ The operator is registered automatically and accepted by `Derivation` / `SpatialDerivation` with no other change.
329
+
330
+ ## Known limitations
331
+
332
+ The limitations below are scope decisions for the current version, not bugs. They are documented so that users and reviewers understand what is implemented versus what is planned.
333
+
334
+ **In-memory, single-tile processing**
335
+ Each call to `derive()` loads a tile's full data into memory. There is no lazy (Dask) or distributed processing. For continental-scale grids (e.g. `BR/1km`), use the tile loop — each tile is processed and saved independently.
336
+
337
+ **Vector aggregation by rasterization (not area-weighted)**
338
+ Operators over vector sources (`majority`, `percentage`, `attribute`, `presence`, `minority`) convert geometries to raster before aggregating pixels. For the share of each cell covered by polygons, use `area`, which intersects the polygons with the cells exactly.
339
+
340
+ **Tile disambiguation in `load()`**
341
+ `CubeClient.load(name)` without `tile_id` raises `ValueError` when multiple tiles of the same variable exist on the same grid. Automatic mosaicking is not implemented. **Always pass `tile_id` in multi-tile workloads.**
342
+
343
+ **`SpatialRelation` does not act in the pipeline**
344
+ The `SpatialRelation` model is persisted in the catalog, but no pipeline stage uses relations during derivation — which is why they are **excluded from `spec_hash`**. Including them would make the cache key sensitive to metadata that does not affect the result, breaking the reproducibility guarantee. Integration with hierarchical grid strategies is reserved for a future version.
345
+
346
+ **`purity_threshold` reserved**
347
+ The `purity_threshold` field on `Derivation` is included in `spec_hash` but is not applied to the output — purity masking is not implemented. Setting `purity_threshold` changes the cache key without changing the result.
348
+
349
+ **STAC: reading only**
350
+ `disscube.sources.bdc` reads Brazil Data Cube cubes through their STAC catalog (search, windowed reads, per-tile composites, mosaics) and writes local GeoTIFFs that are registered as ordinary sources. Derived variables are not published back as STAC, and the `valid_from`/`valid_until` and `bbox` fields on `Derivation` only follow STAC naming conventions.
351
+
352
+ ## Citation
353
+
354
+ If you use DisSCube in your research, dynamic modeling, or spatial data pipelines, please cite it using the metadata from [`CITATION.cff`](CITATION.cff) or the following BibTeX entry:
355
+
356
+ ```bibtex
357
+ @software{costa_disscube_2026,
358
+ author = {Costa, S{\'e}rgio Souza},
359
+ title = {{DisSCube: Declarative spatial data cubes}},
360
+ year = {2026},
361
+ version = {0.4.0},
362
+ url = {https://github.com/DisSModel/disscube}
363
+ }
364
+ ```
365
+
366
+ ## License
367
+
368
+ DisSCube is part of the DisSModel ecosystem and is released under the MIT License. See [LICENSE](LICENSE) for details.