disscube 0.4.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.
- disscube/__init__.py +19 -0
- disscube/catalog/__init__.py +5 -0
- disscube/catalog/json_store.py +94 -0
- disscube/catalog/protocol.py +22 -0
- disscube/catalog/sqlite_store.py +143 -0
- disscube/cli.py +144 -0
- disscube/client.py +521 -0
- disscube/data/bdc_grids/BDC_LG_V2.zip +0 -0
- disscube/data/bdc_grids/BDC_MD_V2.zip +0 -0
- disscube/data/bdc_grids/BDC_SM_V2.zip +0 -0
- disscube/data/bdc_grids/README.md +51 -0
- disscube/export.py +168 -0
- disscube/models/__init__.py +28 -0
- disscube/models/derivation.py +186 -0
- disscube/models/grid.py +240 -0
- disscube/models/variable.py +129 -0
- disscube/operators/__init__.py +20 -0
- disscube/operators/base.py +115 -0
- disscube/operators/proximity.py +170 -0
- disscube/operators/zonal.py +522 -0
- disscube/pipeline/__init__.py +41 -0
- disscube/pipeline/aggregator.py +152 -0
- disscube/pipeline/aligner.py +353 -0
- disscube/pipeline/context.py +24 -0
- disscube/pipeline/normalizer.py +59 -0
- disscube/pipeline/runner.py +693 -0
- disscube/pipeline/schema.py +212 -0
- disscube/pipeline/writer.py +144 -0
- disscube/sources/__init__.py +43 -0
- disscube/sources/_categorical.py +89 -0
- disscube/sources/_raster.py +303 -0
- disscube/sources/bdc.py +286 -0
- disscube/sources/classified.py +80 -0
- disscube/sources/mapbiomas.py +162 -0
- disscube/sources/prodes.py +254 -0
- disscube/storage.py +32 -0
- disscube/utils.py +150 -0
- disscube-0.4.0.dist-info/METADATA +368 -0
- disscube-0.4.0.dist-info/RECORD +43 -0
- disscube-0.4.0.dist-info/WHEEL +5 -0
- disscube-0.4.0.dist-info/entry_points.txt +2 -0
- disscube-0.4.0.dist-info/licenses/LICENSE +21 -0
- disscube-0.4.0.dist-info/top_level.txt +1 -0
disscube/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from disscube.client import CubeClient
|
|
2
|
+
from disscube.models import (
|
|
3
|
+
Derivation,
|
|
4
|
+
DerivedVariable,
|
|
5
|
+
GridSpec,
|
|
6
|
+
SpatialDerivation,
|
|
7
|
+
SpatialSource,
|
|
8
|
+
Variable,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"CubeClient",
|
|
13
|
+
"Derivation",
|
|
14
|
+
"DerivedVariable",
|
|
15
|
+
"GridSpec",
|
|
16
|
+
"SpatialDerivation",
|
|
17
|
+
"SpatialSource",
|
|
18
|
+
"Variable",
|
|
19
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from disscube.models import DerivedVariable, GridSpec, SpatialRelation, SpatialSource
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JsonCatalogStore:
|
|
9
|
+
def __init__(self, path: str | Path):
|
|
10
|
+
self.path = Path(path)
|
|
11
|
+
self._data: dict[str, Any] = {"grids": {}, "sources": {}, "derived": {}, "relations": []}
|
|
12
|
+
if self.path.exists():
|
|
13
|
+
with open(self.path, "r") as f:
|
|
14
|
+
self._data = json.load(f)
|
|
15
|
+
if "relations" not in self._data:
|
|
16
|
+
self._data["relations"] = []
|
|
17
|
+
|
|
18
|
+
def _save(self):
|
|
19
|
+
with open(self.path, "w") as f:
|
|
20
|
+
json.dump(self._data, f, indent=2)
|
|
21
|
+
|
|
22
|
+
def save_grid(self, grid: GridSpec) -> None:
|
|
23
|
+
self._data["grids"][grid.id] = grid.model_dump()
|
|
24
|
+
self._save()
|
|
25
|
+
|
|
26
|
+
def get_grid(self, grid_id: str) -> GridSpec | None:
|
|
27
|
+
data = self._data["grids"].get(grid_id)
|
|
28
|
+
return GridSpec(**data) if data else None
|
|
29
|
+
|
|
30
|
+
def list_grids(self) -> list[GridSpec]:
|
|
31
|
+
return [GridSpec(**g) for g in self._data["grids"].values()]
|
|
32
|
+
|
|
33
|
+
def save_spatial_source(self, source: SpatialSource) -> None:
|
|
34
|
+
self._data["sources"][source.id] = source.model_dump()
|
|
35
|
+
self._save()
|
|
36
|
+
|
|
37
|
+
def get_spatial_source(self, source_id: str) -> SpatialSource | None:
|
|
38
|
+
data = self._data["sources"].get(source_id)
|
|
39
|
+
return SpatialSource(**data) if data else None
|
|
40
|
+
|
|
41
|
+
def list_spatial_sources(self) -> list[SpatialSource]:
|
|
42
|
+
return [SpatialSource(**s) for s in self._data["sources"].values()]
|
|
43
|
+
|
|
44
|
+
def save_derived(self, derived: DerivedVariable) -> None:
|
|
45
|
+
self._data["derived"][derived.id] = derived.model_dump()
|
|
46
|
+
self._save()
|
|
47
|
+
|
|
48
|
+
def delete_derived(self, derived_id: str) -> None:
|
|
49
|
+
self._data["derived"].pop(derived_id, None)
|
|
50
|
+
self._save()
|
|
51
|
+
|
|
52
|
+
def search_derived_variables(self, grid_id: str | None = None, role: str | None = None, tile_id: str | None = None) -> list[DerivedVariable]:
|
|
53
|
+
results = []
|
|
54
|
+
for d in self._data["derived"].values():
|
|
55
|
+
if grid_id and d["grid_id"] != grid_id:
|
|
56
|
+
continue
|
|
57
|
+
if role and d["role"] != role:
|
|
58
|
+
continue
|
|
59
|
+
if tile_id and d.get("tile_id") != tile_id:
|
|
60
|
+
continue
|
|
61
|
+
results.append(DerivedVariable(**d))
|
|
62
|
+
return results
|
|
63
|
+
|
|
64
|
+
def get_derived_by_hash(self, spec_hash: str) -> DerivedVariable | None:
|
|
65
|
+
for d in self._data["derived"].values():
|
|
66
|
+
if d["spec_hash"] == spec_hash:
|
|
67
|
+
return DerivedVariable(**d)
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def save_relation(self, relation: SpatialRelation) -> None:
|
|
71
|
+
# Check if already exists to avoid duplicates
|
|
72
|
+
existing = self.get_relation(relation.source_grid_id, relation.target_grid_id)
|
|
73
|
+
if existing:
|
|
74
|
+
# Update
|
|
75
|
+
for i, r in enumerate(self._data["relations"]):
|
|
76
|
+
if r["source_grid_id"] == relation.source_grid_id and r["target_grid_id"] == relation.target_grid_id:
|
|
77
|
+
self._data["relations"][i] = relation.model_dump()
|
|
78
|
+
break
|
|
79
|
+
else:
|
|
80
|
+
self._data["relations"].append(relation.model_dump())
|
|
81
|
+
self._save()
|
|
82
|
+
|
|
83
|
+
def get_relations(self, grid_id: str) -> list[SpatialRelation]:
|
|
84
|
+
"""Busca bidirecional: source_grid_id OU target_grid_id."""
|
|
85
|
+
return [
|
|
86
|
+
SpatialRelation(**r) for r in self._data["relations"]
|
|
87
|
+
if r["source_grid_id"] == grid_id or r["target_grid_id"] == grid_id
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
def get_relation(self, source_id: str, target_id: str) -> SpatialRelation | None:
|
|
91
|
+
for r in self._data["relations"]:
|
|
92
|
+
if r["source_grid_id"] == source_id and r["target_grid_id"] == target_id:
|
|
93
|
+
return SpatialRelation(**r)
|
|
94
|
+
return None
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
|
|
3
|
+
from disscube.models import DerivedVariable, GridSpec, SpatialRelation, SpatialSource
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CatalogStore(Protocol):
|
|
7
|
+
def save_grid(self, grid: GridSpec) -> None: ...
|
|
8
|
+
def get_grid(self, grid_id: str) -> GridSpec | None: ...
|
|
9
|
+
def list_grids(self) -> list[GridSpec]: ...
|
|
10
|
+
|
|
11
|
+
def save_spatial_source(self, source: SpatialSource) -> None: ...
|
|
12
|
+
def get_spatial_source(self, source_id: str) -> SpatialSource | None: ...
|
|
13
|
+
def list_spatial_sources(self) -> list[SpatialSource]: ...
|
|
14
|
+
|
|
15
|
+
def save_derived(self, derived: DerivedVariable) -> None: ...
|
|
16
|
+
def delete_derived(self, derived_id: str) -> None: ...
|
|
17
|
+
def search_derived_variables(self, grid_id: str | None = None, role: str | None = None, tile_id: str | None = None) -> list[DerivedVariable]: ...
|
|
18
|
+
def get_derived_by_hash(self, spec_hash: str) -> DerivedVariable | None: ...
|
|
19
|
+
|
|
20
|
+
def save_relation(self, relation: SpatialRelation) -> None: ...
|
|
21
|
+
def get_relations(self, grid_id: str) -> list[SpatialRelation]: ...
|
|
22
|
+
def get_relation(self, source_id: str, target_id: str) -> SpatialRelation | None: ...
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sqlite3
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from disscube.models import DerivedVariable, GridSpec, SpatialRelation, SpatialSource
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SqliteCatalogStore:
|
|
9
|
+
def __init__(self, path: str | Path):
|
|
10
|
+
self.path = Path(path)
|
|
11
|
+
self._init_db()
|
|
12
|
+
|
|
13
|
+
def _get_connection(self):
|
|
14
|
+
conn = sqlite3.connect(self.path)
|
|
15
|
+
conn.row_factory = sqlite3.Row
|
|
16
|
+
return conn
|
|
17
|
+
|
|
18
|
+
def _init_db(self):
|
|
19
|
+
with self._get_connection() as conn:
|
|
20
|
+
conn.execute("""
|
|
21
|
+
CREATE TABLE IF NOT EXISTS grids (
|
|
22
|
+
id TEXT PRIMARY KEY,
|
|
23
|
+
data TEXT NOT NULL
|
|
24
|
+
)
|
|
25
|
+
""")
|
|
26
|
+
conn.execute("""
|
|
27
|
+
CREATE TABLE IF NOT EXISTS sources (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
data TEXT NOT NULL
|
|
30
|
+
)
|
|
31
|
+
""")
|
|
32
|
+
conn.execute("""
|
|
33
|
+
CREATE TABLE IF NOT EXISTS derived (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
grid_id TEXT,
|
|
36
|
+
spec_hash TEXT,
|
|
37
|
+
tile_id TEXT,
|
|
38
|
+
role TEXT,
|
|
39
|
+
data TEXT NOT NULL
|
|
40
|
+
)
|
|
41
|
+
""")
|
|
42
|
+
conn.execute("""
|
|
43
|
+
CREATE TABLE IF NOT EXISTS relations (
|
|
44
|
+
source_grid_id TEXT,
|
|
45
|
+
target_grid_id TEXT,
|
|
46
|
+
data TEXT NOT NULL,
|
|
47
|
+
PRIMARY KEY (source_grid_id, target_grid_id)
|
|
48
|
+
)
|
|
49
|
+
""")
|
|
50
|
+
# Indexes for performance
|
|
51
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_derived_grid ON derived(grid_id)")
|
|
52
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_derived_hash ON derived(spec_hash)")
|
|
53
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_derived_tile ON derived(tile_id)")
|
|
54
|
+
|
|
55
|
+
def save_grid(self, grid: GridSpec) -> None:
|
|
56
|
+
with self._get_connection() as conn:
|
|
57
|
+
conn.execute(
|
|
58
|
+
"INSERT OR REPLACE INTO grids (id, data) VALUES (?, ?)",
|
|
59
|
+
(grid.id, grid.model_dump_json())
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def get_grid(self, grid_id: str) -> GridSpec | None:
|
|
63
|
+
with self._get_connection() as conn:
|
|
64
|
+
row = conn.execute("SELECT data FROM grids WHERE id = ?", (grid_id,)).fetchone()
|
|
65
|
+
return GridSpec(**json.loads(row["data"])) if row else None
|
|
66
|
+
|
|
67
|
+
def list_grids(self) -> list[GridSpec]:
|
|
68
|
+
with self._get_connection() as conn:
|
|
69
|
+
rows = conn.execute("SELECT data FROM grids").fetchall()
|
|
70
|
+
return [GridSpec(**json.loads(r["data"])) for r in rows]
|
|
71
|
+
|
|
72
|
+
def save_spatial_source(self, source: SpatialSource) -> None:
|
|
73
|
+
with self._get_connection() as conn:
|
|
74
|
+
conn.execute(
|
|
75
|
+
"INSERT OR REPLACE INTO sources (id, data) VALUES (?, ?)",
|
|
76
|
+
(source.id, source.model_dump_json())
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def get_spatial_source(self, source_id: str) -> SpatialSource | None:
|
|
80
|
+
with self._get_connection() as conn:
|
|
81
|
+
row = conn.execute("SELECT data FROM sources WHERE id = ?", (source_id,)).fetchone()
|
|
82
|
+
return SpatialSource(**json.loads(row["data"])) if row else None
|
|
83
|
+
|
|
84
|
+
def list_spatial_sources(self) -> list[SpatialSource]:
|
|
85
|
+
with self._get_connection() as conn:
|
|
86
|
+
rows = conn.execute("SELECT data FROM sources").fetchall()
|
|
87
|
+
return [SpatialSource(**json.loads(r["data"])) for r in rows]
|
|
88
|
+
|
|
89
|
+
def save_derived(self, derived: DerivedVariable) -> None:
|
|
90
|
+
with self._get_connection() as conn:
|
|
91
|
+
conn.execute(
|
|
92
|
+
"INSERT OR REPLACE INTO derived (id, grid_id, spec_hash, tile_id, role, data) VALUES (?, ?, ?, ?, ?, ?)",
|
|
93
|
+
(derived.id, derived.grid_id, derived.spec_hash, derived.tile_id, derived.role, derived.model_dump_json())
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def delete_derived(self, derived_id: str) -> None:
|
|
97
|
+
with self._get_connection() as conn:
|
|
98
|
+
conn.execute("DELETE FROM derived WHERE id = ?", (derived_id,))
|
|
99
|
+
|
|
100
|
+
def search_derived_variables(self, grid_id: str | None = None, role: str | None = None, tile_id: str | None = None) -> list[DerivedVariable]:
|
|
101
|
+
query = "SELECT data FROM derived WHERE 1=1"
|
|
102
|
+
params = []
|
|
103
|
+
if grid_id:
|
|
104
|
+
query += " AND grid_id = ?"
|
|
105
|
+
params.append(grid_id)
|
|
106
|
+
if role:
|
|
107
|
+
query += " AND role = ?"
|
|
108
|
+
params.append(role)
|
|
109
|
+
if tile_id:
|
|
110
|
+
query += " AND tile_id = ?"
|
|
111
|
+
params.append(tile_id)
|
|
112
|
+
|
|
113
|
+
with self._get_connection() as conn:
|
|
114
|
+
rows = conn.execute(query, params).fetchall()
|
|
115
|
+
return [DerivedVariable(**json.loads(r["data"])) for r in rows]
|
|
116
|
+
|
|
117
|
+
def get_derived_by_hash(self, spec_hash: str) -> DerivedVariable | None:
|
|
118
|
+
with self._get_connection() as conn:
|
|
119
|
+
row = conn.execute("SELECT data FROM derived WHERE spec_hash = ?", (spec_hash,)).fetchone()
|
|
120
|
+
return DerivedVariable(**json.loads(row["data"])) if row else None
|
|
121
|
+
|
|
122
|
+
def save_relation(self, relation: SpatialRelation) -> None:
|
|
123
|
+
with self._get_connection() as conn:
|
|
124
|
+
conn.execute(
|
|
125
|
+
"INSERT OR REPLACE INTO relations (source_grid_id, target_grid_id, data) VALUES (?, ?, ?)",
|
|
126
|
+
(relation.source_grid_id, relation.target_grid_id, relation.model_dump_json())
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def get_relations(self, grid_id: str) -> list[SpatialRelation]:
|
|
130
|
+
with self._get_connection() as conn:
|
|
131
|
+
rows = conn.execute(
|
|
132
|
+
"SELECT data FROM relations WHERE source_grid_id = ? OR target_grid_id = ?",
|
|
133
|
+
(grid_id, grid_id)
|
|
134
|
+
)
|
|
135
|
+
return [SpatialRelation(**json.loads(r["data"])) for r in rows]
|
|
136
|
+
|
|
137
|
+
def get_relation(self, source_id: str, target_id: str) -> SpatialRelation | None:
|
|
138
|
+
with self._get_connection() as conn:
|
|
139
|
+
row = conn.execute(
|
|
140
|
+
"SELECT data FROM relations WHERE source_grid_id = ? AND target_grid_id = ?",
|
|
141
|
+
(source_id, target_id)
|
|
142
|
+
).fetchone()
|
|
143
|
+
return SpatialRelation(**json.loads(row["data"])) if row else None
|
disscube/cli.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command line: run DisSCube pipeline files.
|
|
3
|
+
|
|
4
|
+
disscube validate pipeline.toml [--json]
|
|
5
|
+
disscube fetch pipeline.toml
|
|
6
|
+
disscube run pipeline.toml [--workspace DIR] [--output OUT.tif] [--dry-run] [--json] [-v]
|
|
7
|
+
disscube export pipeline.toml --output OUT.tif [--workspace DIR] [--variables ...] [--json] [-v]
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import logging
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main(argv: list[str] | None = None) -> int:
|
|
18
|
+
parser = argparse.ArgumentParser(prog="disscube", description="Run DisSCube pipeline files.")
|
|
19
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
20
|
+
|
|
21
|
+
p_val = sub.add_parser("validate", help="check a pipeline file without fetching anything")
|
|
22
|
+
p_val.add_argument("file")
|
|
23
|
+
p_val.add_argument("--json", action="store_true", help="output validation result as JSON")
|
|
24
|
+
|
|
25
|
+
p_fetch = sub.add_parser("fetch", help="download and verify remote file sources declared in the pipeline")
|
|
26
|
+
p_fetch.add_argument("file")
|
|
27
|
+
|
|
28
|
+
p_run = sub.add_parser("run", help="fetch the sources and derive the variables")
|
|
29
|
+
p_run.add_argument("file")
|
|
30
|
+
p_run.add_argument("--workspace", help="output folder (default: the file's 'workspace', "
|
|
31
|
+
"else a folder named after the file)")
|
|
32
|
+
p_run.add_argument("--output", "-o", help="export derived variables (GeoTIFF; netCDF if the file ends in .nc)")
|
|
33
|
+
p_run.add_argument("--dry-run", action="store_true", help="simulate plan execution without downloading or computing")
|
|
34
|
+
p_run.add_argument("--json", action="store_true", help="output execution report as JSON")
|
|
35
|
+
p_run.add_argument("-v", "--verbose", action="store_true", help="log each step")
|
|
36
|
+
|
|
37
|
+
p_exp = sub.add_parser("export", help="export derived variables from an existing data cube to GeoTIFF or netCDF")
|
|
38
|
+
p_exp.add_argument("file", help="pipeline TOML file")
|
|
39
|
+
p_exp.add_argument("--output", "-o", required=True, help="output file: GeoTIFF, or netCDF if it ends in .nc (e.g. data/cellspace.tif)")
|
|
40
|
+
p_exp.add_argument("--workspace", help="workspace folder (default: data/cube or from pipeline)")
|
|
41
|
+
p_exp.add_argument("--variables", nargs="*", help="specific variables to export (default: all derived)")
|
|
42
|
+
p_exp.add_argument("--json", action="store_true", help="output export result as JSON")
|
|
43
|
+
p_exp.add_argument("-v", "--verbose", action="store_true", help="log each step")
|
|
44
|
+
|
|
45
|
+
args = parser.parse_args(argv)
|
|
46
|
+
from disscube.pipeline import PipelineError, plan, run
|
|
47
|
+
|
|
48
|
+
logging.basicConfig(level=logging.INFO if getattr(args, "verbose", False) else logging.WARNING,
|
|
49
|
+
format="%(levelname)s %(name)s: %(message)s")
|
|
50
|
+
is_json = getattr(args, "json", False)
|
|
51
|
+
try:
|
|
52
|
+
p = plan(args.file)
|
|
53
|
+
if not is_json:
|
|
54
|
+
print(p.summary())
|
|
55
|
+
if args.command == "fetch":
|
|
56
|
+
from disscube.pipeline.runner import _fetch_file_source, _local_file, _raw_cache_dir, _resolve
|
|
57
|
+
from disscube.pipeline.schema import FileSource
|
|
58
|
+
raw = _raw_cache_dir()
|
|
59
|
+
raw.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
fetched = 0
|
|
61
|
+
for s in p.sources:
|
|
62
|
+
if isinstance(s.config, FileSource) and getattr(s.config, 'url', None):
|
|
63
|
+
target = _local_file(_resolve(raw, s.config.path))
|
|
64
|
+
if target is not None:
|
|
65
|
+
print(f"Fetching {s.id} -> {target.name}...")
|
|
66
|
+
_fetch_file_source(s.config, target)
|
|
67
|
+
fetched += 1
|
|
68
|
+
print(f"Fetch completed: {fetched} remote sources verified.")
|
|
69
|
+
return 0
|
|
70
|
+
if args.command == "validate":
|
|
71
|
+
if is_json:
|
|
72
|
+
import json
|
|
73
|
+
print(json.dumps({
|
|
74
|
+
"status": "ok",
|
|
75
|
+
"file": str(p.file.path),
|
|
76
|
+
"name": p.file.config.name,
|
|
77
|
+
"grid": p.grid.name if p.grid else None,
|
|
78
|
+
"sources": [s.id for s in p.sources],
|
|
79
|
+
"derives": [d.target for d in p.derives],
|
|
80
|
+
}, indent=2))
|
|
81
|
+
else:
|
|
82
|
+
print("OK")
|
|
83
|
+
return 0
|
|
84
|
+
if getattr(args, "dry_run", False):
|
|
85
|
+
if is_json:
|
|
86
|
+
import json
|
|
87
|
+
print(json.dumps({
|
|
88
|
+
"status": "ok",
|
|
89
|
+
"dry_run": True,
|
|
90
|
+
"file": str(p.file.path),
|
|
91
|
+
"grid": p.grid.name if p.grid else None,
|
|
92
|
+
"sources": [{"id": s.id, "type": s.config.type} for s in p.sources],
|
|
93
|
+
"derives": [{"target": d.target, "source": d.source, "operator": d.operator} for d in p.derives],
|
|
94
|
+
}, indent=2))
|
|
95
|
+
else:
|
|
96
|
+
print(f"[dry-run] Plan is valid. Would process {len(p.sources)} sources and derive {len(p.derives)} variables.")
|
|
97
|
+
return 0
|
|
98
|
+
if args.command == "export":
|
|
99
|
+
from disscube.pipeline import export_cube
|
|
100
|
+
exp = export_cube(p, output=args.output, workspace=args.workspace, variables=args.variables)
|
|
101
|
+
if is_json:
|
|
102
|
+
import json
|
|
103
|
+
print(json.dumps({
|
|
104
|
+
"status": "ok",
|
|
105
|
+
"workspace": str(exp.workspace),
|
|
106
|
+
"grid_id": exp.grid_id,
|
|
107
|
+
"variables": exp.variables,
|
|
108
|
+
"output": str(exp.output),
|
|
109
|
+
}, indent=2))
|
|
110
|
+
else:
|
|
111
|
+
print(f"workspace : {exp.workspace}")
|
|
112
|
+
print(f"exported : {len(exp.variables)} variables on {exp.grid_id}")
|
|
113
|
+
print(f"output : {exp.output}")
|
|
114
|
+
return 0
|
|
115
|
+
report = run(p, workspace=args.workspace, export_geotiff=getattr(args, "output", None))
|
|
116
|
+
if is_json:
|
|
117
|
+
import json
|
|
118
|
+
print(json.dumps({
|
|
119
|
+
"status": "ok",
|
|
120
|
+
"workspace": str(report.workspace),
|
|
121
|
+
"grid_id": report.grid_id,
|
|
122
|
+
"sources": report.sources,
|
|
123
|
+
"derived": report.derived,
|
|
124
|
+
"exported": str(report.exported) if report.exported else None,
|
|
125
|
+
"record": str(report.record),
|
|
126
|
+
}, indent=2, default=str))
|
|
127
|
+
return 0
|
|
128
|
+
except PipelineError as exc:
|
|
129
|
+
if is_json:
|
|
130
|
+
import json
|
|
131
|
+
print(json.dumps({"status": "error", "error": str(exc)}, indent=2), file=sys.stderr)
|
|
132
|
+
else:
|
|
133
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
134
|
+
return 2
|
|
135
|
+
print(f"workspace : {report.workspace}")
|
|
136
|
+
print(f"derived : {len(report.derived)} products on {report.grid_id}")
|
|
137
|
+
if report.exported:
|
|
138
|
+
print(f"exported : {report.exported}")
|
|
139
|
+
print(f"record : {report.record}")
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__": # pragma: no cover
|
|
144
|
+
sys.exit(main())
|