Skip to content

Commit 9c3b7f5

Browse files
metzmmmacata
andauthored
add support for local external collections (#154)
* add support for local external collections Co-authored-by: Carmen Tawalika <mmacata@users.noreply.github.com>
1 parent e6e93e1 commit 9c3b7f5

File tree

7 files changed

+551
-331
lines changed

7 files changed

+551
-331
lines changed

.flake8

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ per-file-ignores =
1313
./src/openeo_grass_gis_driver/models/job_schemas.py: W605, E501
1414
./src/openeo_grass_gis_driver/models/service_schemas.py: W605
1515
./src/openeo_grass_gis_driver/collection_information.py: E501
16+
./src/openeo_grass_gis_driver/local_collections.py: E501
1617
./src/openeo_grass_gis_driver/jobs_job_id.py: E501
1718
./src/openeo_grass_gis_driver/jobs_job_id_estimate.py: E501
1819
./src/openeo_grass_gis_driver/jobs_job_id_logs.py: E501

src/openeo_grass_gis_driver/actinia_processing/base.py

Lines changed: 127 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

77
# This is the process dictionary that is used to store all processes of
88
# the Actinia wrapper
9-
from openeo_grass_gis_driver.actinia_processing.actinia_interface import \
10-
ActiniaInterface
9+
from openeo_grass_gis_driver.actinia_processing.actinia_interface import (
10+
ActiniaInterface,
11+
)
1112

1213
from openeo_grass_gis_driver.models.process_graph_schemas import ProcessGraph
1314

@@ -62,7 +63,14 @@ class GrassDataType(Enum):
6263
RASTER = "raster"
6364
VECTOR = "vector"
6465
STRDS = "strds"
65-
STAC = "rastercube"
66+
EXTERN = "rastercube"
67+
68+
69+
class DataSource(Enum):
70+
71+
GRASS = "grass"
72+
STAC = "stac"
73+
LOCAL = "gdallocal"
6674

6775

6876
class DataObject:
@@ -71,14 +79,17 @@ class DataObject:
7179
"""
7280

7381
def __init__(
74-
self,
75-
name: str,
76-
datatype: GrassDataType,
77-
mapset: str = None,
78-
location: str = None):
82+
self,
83+
name: str,
84+
datatype: GrassDataType,
85+
datasource: DataSource = DataSource.GRASS,
86+
mapset: str = None,
87+
location: str = None,
88+
):
7989

8090
self.name = name
8191
self.datatype = datatype
92+
self.datasource = datasource
8293
self.mapset = mapset
8394
self.location = location
8495

@@ -90,38 +101,67 @@ def __str__(self):
90101
def from_string(name: str):
91102

92103
AI = ActiniaInterface
93-
location, mapset, datatype, layer_name = AI.layer_def_to_components(
94-
name)
104+
location, mapset, datatype, layer_name = AI.layer_def_to_components(name)
95105

96106
if datatype is None:
97107
raise Exception(f"Invalid collection id <{name}>")
98108

99-
if GrassDataType.RASTER.value == datatype:
109+
datasource = DataSource.GRASS
110+
if location == "stac":
111+
datasource = DataSource.STAC
112+
elif location == "local":
113+
datasource = DataSource.LOCAL
114+
115+
if DataSource.GRASS == datasource and GrassDataType.RASTER.value == datatype:
100116
return DataObject(
101117
name=layer_name,
102118
datatype=GrassDataType.RASTER,
119+
datasource=datasource,
103120
mapset=mapset,
104-
location=location)
105-
elif GrassDataType.VECTOR.value == datatype:
121+
location=location,
122+
)
123+
elif DataSource.GRASS == datasource and GrassDataType.VECTOR.value == datatype:
106124
return DataObject(
107125
name=layer_name,
108126
datatype=GrassDataType.VECTOR,
127+
datasource=datasource,
109128
mapset=mapset,
110-
location=location)
111-
elif GrassDataType.STRDS.value == datatype:
129+
location=location,
130+
)
131+
elif DataSource.GRASS == datasource and GrassDataType.STRDS.value == datatype:
112132
return DataObject(
113133
name=layer_name,
114134
datatype=GrassDataType.STRDS,
135+
datasource=datasource,
115136
mapset=mapset,
116-
location=location)
117-
elif GrassDataType.STAC.value == datatype:
137+
location=location,
138+
)
139+
elif DataSource.STAC == datasource and GrassDataType.EXTERN.value == datatype:
140+
# location = "stac"
141+
del AI.PROCESS_LOCATION[location]
142+
AI.PROCESS_LOCATION["latlong_wgs84"] = "latlong_wgs84"
118143
return DataObject(
119144
name=layer_name,
120-
datatype=GrassDataType.STAC,
145+
datatype=GrassDataType.EXTERN,
146+
datasource=datasource,
121147
mapset=mapset,
122-
location="latlong_wgs84")
148+
location="latlong_wgs84",
149+
)
150+
elif DataSource.LOCAL == datasource and GrassDataType.EXTERN.value == datatype:
151+
# location = "local"
152+
del AI.PROCESS_LOCATION[location]
153+
AI.PROCESS_LOCATION["latlong_wgs84"] = "latlong_wgs84"
154+
return DataObject(
155+
name=layer_name,
156+
datatype=GrassDataType.EXTERN,
157+
datasource=datasource,
158+
mapset=mapset,
159+
location="latlong_wgs84",
160+
)
123161

124-
raise Exception(f"Unsupported object type <{datatype}>")
162+
raise Exception(
163+
f"Unsupported object type <{datatype}> and data source <{datasource.value}>"
164+
)
125165

126166
def grass_name(self):
127167

@@ -134,24 +174,40 @@ def full_name(self):
134174

135175
def is_strds(self):
136176

137-
return self.datatype == GrassDataType.STRDS
177+
return (
178+
self.datasource == DataSource.GRASS and self.datatype == GrassDataType.STRDS
179+
)
138180

139181
def is_raster(self):
140182

141-
return self.datatype == GrassDataType.RASTER
183+
return (
184+
self.datasource == DataSource.GRASS
185+
and self.datatype == GrassDataType.RASTER
186+
)
142187

143188
def is_vector(self):
144189

145-
return self.datatype == GrassDataType.VECTOR
190+
return (
191+
self.datasource == DataSource.GRASS
192+
and self.datatype == GrassDataType.VECTOR
193+
)
146194

147195
def is_stac(self):
148196

149-
return self.datatype == GrassDataType.STAC
197+
return (
198+
self.datasource == DataSource.STAC and self.datatype == GrassDataType.EXTERN
199+
)
200+
201+
def is_local(self):
202+
203+
return (
204+
self.datasource == DataSource.LOCAL
205+
and self.datatype == GrassDataType.EXTERN
206+
)
150207

151208

152209
class Node:
153-
"""A single node in the process graph
154-
"""
210+
"""A single node in the process graph"""
155211

156212
def __init__(self, id, process_description: dict):
157213

@@ -171,7 +227,7 @@ def __init__(self, id, process_description: dict):
171227
def add_output(self, output_object: DataObject):
172228
self.output_objects.add(output_object)
173229

174-
def get_parent_by_name(self, parent_name: str) -> Optional['Node']:
230+
def get_parent_by_name(self, parent_name: str) -> Optional["Node"]:
175231
if parent_name in self.parents_dict.keys():
176232
return self.parents_dict[parent_name]
177233
return None
@@ -198,8 +254,10 @@ def __str__(self):
198254
if self.parents_dict:
199255
parent_names = list(self.parents_dict.keys())
200256

201-
return (f"Node: {self.id} parent names: {parent_names} parent "
202-
f"ids: {parent_ids} child ids: {child_ids}")
257+
return (
258+
f"Node: {self.id} parent names: {parent_names} parent "
259+
f"ids: {parent_ids} child ids: {child_ids}"
260+
)
203261

204262
def get_parents_dict(self) -> dict:
205263

@@ -220,9 +278,7 @@ def as_dict(self) -> dict:
220278

221279

222280
class Graph:
223-
"""This class represents a process graph
224-
225-
"""
281+
"""This class represents a process graph"""
226282

227283
def __init__(self, graph_description: Union[Dict, ProcessGraph]):
228284
"""The constructor checks the validity of the provided dictionary
@@ -237,7 +293,8 @@ def __init__(self, graph_description: Union[Dict, ProcessGraph]):
237293
self.title: str = graph_description.title
238294
self.description: str = graph_description.description
239295
self.build_process_graph_from_description(
240-
process_graph=graph_description.process_graph)
296+
process_graph=graph_description.process_graph
297+
)
241298
else:
242299
if "title" not in graph_description:
243300
# raise Exception("Title is required in the process graph")
@@ -250,13 +307,16 @@ def __init__(self, graph_description: Union[Dict, ProcessGraph]):
250307

251308
# graph_description can be a process with process_graph or
252309
# only a process_graph
253-
if "process" not in graph_description and \
254-
"process_graph" not in graph_description:
255-
raise Exception(
256-
"process_graph is required in the process graph")
257-
258-
if "process" in graph_description and \
259-
"process_graph" not in graph_description["process"]:
310+
if (
311+
"process" not in graph_description
312+
and "process_graph" not in graph_description
313+
):
314+
raise Exception("process_graph is required in the process graph")
315+
316+
if (
317+
"process" in graph_description
318+
and "process_graph" not in graph_description["process"]
319+
):
260320
raise Exception("process_graph is required in the process")
261321

262322
if "process" in graph_description:
@@ -266,8 +326,7 @@ def __init__(self, graph_description: Union[Dict, ProcessGraph]):
266326
self.title: str = graph_description["title"]
267327
self.description: str = graph_description["description"]
268328

269-
self.build_process_graph_from_description(
270-
process_graph=process_graph)
329+
self.build_process_graph_from_description(process_graph=process_graph)
271330

272331
def build_process_graph_from_description(self, process_graph: dict):
273332
"""Build the directed process graph from the graph description
@@ -425,31 +484,30 @@ def openeo_to_actinia(node: Node) -> Tuple[list, list]:
425484
# warning, error, exception?
426485
continue
427486
# check if it is an input object
428-
if isinstance(node.arguments[key], dict) and \
429-
"from_node" in node.arguments[key]:
487+
if isinstance(node.arguments[key], dict) and "from_node" in node.arguments[key]:
430488
# input option comes from another node in the process graph
431489
# which output object in the set of output_objects?
432-
value = list(
433-
node.get_parent_by_name(
434-
parent_name=key).output_objects)[0]
490+
value = list(node.get_parent_by_name(parent_name=key).output_objects)[0]
435491
data_object = value
436492
# check schema subtype of parameter and compare with
437493
# datatype of data_object
438-
if ao["schema"]["subtype"] == "cell" and \
439-
data_object.datatype != GrassDataType.RASTER:
440-
raise Exception(
441-
"Wrong input data type, expecting 'cell'")
442-
elif ao["schema"]["subtype"] == "strds" and \
443-
data_object.datatype != GrassDataType.STRDS:
444-
raise Exception(
445-
"Wrong input data type, expecting 'strds'")
446-
elif ao["schema"]["subtype"] == "vector" and \
447-
data_object.datatype != GrassDataType.VECTOR:
448-
raise Exception(
449-
"Wrong input data type, expecting 'vector'")
450-
451-
param = {"param": key,
452-
"value": data_object.grass_name()}
494+
if (
495+
ao["schema"]["subtype"] == "cell"
496+
and data_object.datatype != GrassDataType.RASTER
497+
):
498+
raise Exception("Wrong input data type, expecting 'cell'")
499+
elif (
500+
ao["schema"]["subtype"] == "strds"
501+
and data_object.datatype != GrassDataType.STRDS
502+
):
503+
raise Exception("Wrong input data type, expecting 'strds'")
504+
elif (
505+
ao["schema"]["subtype"] == "vector"
506+
and data_object.datatype != GrassDataType.VECTOR
507+
):
508+
raise Exception("Wrong input data type, expecting 'vector'")
509+
510+
param = {"param": key, "value": data_object.grass_name()}
453511
pc["inputs"].append(param)
454512
elif ao["schema"]["type"] == "boolean":
455513
# flag
@@ -461,18 +519,15 @@ def openeo_to_actinia(node: Node) -> Tuple[list, list]:
461519
else:
462520
# option answer, treat as string
463521
value = node.arguments[key]
464-
param = {"param": key,
465-
"value": str(value)}
522+
param = {"param": key, "value": str(value)}
466523
pc["inputs"].append(param)
467524

468525
if pc["flags"] is None:
469526
del pc["flags"]
470527

471528
# TODO: support modules that do not have input maps
472529
if data_object is None:
473-
raise Exception(
474-
"No input data object for actinia process '%s'" %
475-
module_name)
530+
raise Exception("No input data object for actinia process '%s'" % module_name)
476531

477532
# output parameters
478533
if "returns" in module and openeo_returns is not None:
@@ -499,16 +554,14 @@ def openeo_to_actinia(node: Node) -> Tuple[list, list]:
499554
# in order to distinguish between different outputs
500555
# of the same module
501556
output_object = DataObject(
502-
name=create_output_name(data_object.name, node),
503-
datatype=datatype)
504-
param = {"param": key,
505-
"value": output_object.grass_name()}
557+
name=create_output_name(data_object.name, node), datatype=datatype
558+
)
559+
param = {"param": key, "value": output_object.grass_name()}
506560
pc["inputs"].append(param)
507561
output_objects.append(output_object)
508562
node.add_output(output_object=output_object)
509563
if module_name in T_BASENAME_MODULES_LIST:
510-
param = {"param": "basename",
511-
"value": output_object.name}
564+
param = {"param": "basename", "value": output_object.name}
512565
pc["inputs"].append(param)
513566

514567
process_list.append(pc)
@@ -532,7 +585,7 @@ def create_output_name(input: str, node: Node):
532585
new_uuid = uuid.uuid4().hex
533586

534587
# shorter version: only uuid + node id
535-
node_id = node.id.lower().replace(' ', '_')
588+
node_id = node.id.lower().replace(" ", "_")
536589
output = f"uuid{new_uuid}_{node_id}"
537590

538591
return output

src/openeo_grass_gis_driver/actinia_processing/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class ACTINIA:
3737
# The database file that stores the actinia jobs
3838
ACTINIA_JOB_DB = "%s/.actinia_job_db_file.sqlite" % os.environ["HOME"]
3939
SECRET_KEY = "jaNguzeef4seiv5shahchimoo8teiLah"
40+
# path to json files with collection definitions
41+
LOCAL_COLLECTIONS = "%s/.openeo_local_collections" % os.environ["HOME"]
4042

4143

4244
class Configfile:

0 commit comments

Comments
 (0)