From 16fdd2f1df091781306d159672f9371ed2ba2a4d Mon Sep 17 00:00:00 2001 From: huhabla Date: Sat, 25 Aug 2018 13:09:28 +0200 Subject: [PATCH] OpenEO v0.3 preparation --- .coveragerc | 2 +- docs/conf.py | 8 +- requirements.txt | 28 + setup.cfg | 8 +- setup.py | 6 +- src/__init__.py | 0 src/graas_openeo_core_wrapper/capabilities.py | 28 - src/graas_openeo_core_wrapper/data.py | 62 --- src/graas_openeo_core_wrapper/processes.py | 18 - .../processes_process_id.py | 22 - src/graas_openeo_core_wrapper/udf.py | 35 -- .../udf_lang_udf_type.py | 37 -- .../__init__.py | 0 .../actinia_processing}/__init__.py | 18 +- .../actinia_processing/actinia_interface.py} | 14 +- .../actinia_processing}/config.py | 9 +- .../filter_bbox_process.py | 13 +- .../filter_daterange_process.py | 24 +- .../actinia_processing}/min_time_process.py | 19 +- .../actinia_processing}/ndvi_process.py | 22 +- .../actinia_processing}/raster_exporter.py | 17 +- .../actinia_processing}/udf_reduce_time.py | 23 +- .../actinia_processing}/zonal_statistics.py | 16 +- src/openeo_grass_gis_driver/app.py | 13 + src/openeo_grass_gis_driver/capabilities.py | 42 ++ src/openeo_grass_gis_driver/data.py | 128 +++++ .../data_product_id.py | 189 ++++++- src/openeo_grass_gis_driver/definitions.py | 510 ++++++++++++++++++ .../endpoints.py | 20 +- .../graph_db.py | 6 +- .../jobs.py | 83 ++- .../jobs_job_id.py | 92 +++- .../main.py | 4 +- src/openeo_grass_gis_driver/processes.py | 59 ++ .../processes_process_id.py | 54 ++ .../test_base.py | 8 +- src/openeo_grass_gis_driver/udf.py | 105 ++++ .../udf_lang_udf_type.py | 76 +++ tests/conftest.py | 2 +- tests/test_capabilities.py | 4 +- 40 files changed, 1465 insertions(+), 359 deletions(-) create mode 100644 src/__init__.py delete mode 100644 src/graas_openeo_core_wrapper/capabilities.py delete mode 100644 src/graas_openeo_core_wrapper/data.py delete mode 100644 src/graas_openeo_core_wrapper/processes.py delete mode 100644 src/graas_openeo_core_wrapper/processes_process_id.py delete mode 100644 src/graas_openeo_core_wrapper/udf.py delete mode 100644 src/graas_openeo_core_wrapper/udf_lang_udf_type.py rename src/{graas_openeo_core_wrapper => openeo_grass_gis_driver}/__init__.py (100%) rename src/{graas_openeo_core_wrapper/process_definitions => openeo_grass_gis_driver/actinia_processing}/__init__.py (76%) rename src/{graas_openeo_core_wrapper/graas_interface.py => openeo_grass_gis_driver/actinia_processing/actinia_interface.py} (96%) rename src/{graas_openeo_core_wrapper => openeo_grass_gis_driver/actinia_processing}/config.py (79%) rename src/{graas_openeo_core_wrapper/process_definitions => openeo_grass_gis_driver/actinia_processing}/filter_bbox_process.py (85%) rename src/{graas_openeo_core_wrapper/process_definitions => openeo_grass_gis_driver/actinia_processing}/filter_daterange_process.py (74%) rename src/{graas_openeo_core_wrapper/process_definitions => openeo_grass_gis_driver/actinia_processing}/min_time_process.py (73%) rename src/{graas_openeo_core_wrapper/process_definitions => openeo_grass_gis_driver/actinia_processing}/ndvi_process.py (79%) rename src/{graas_openeo_core_wrapper/process_definitions => openeo_grass_gis_driver/actinia_processing}/raster_exporter.py (77%) rename src/{graas_openeo_core_wrapper/process_definitions => openeo_grass_gis_driver/actinia_processing}/udf_reduce_time.py (73%) rename src/{graas_openeo_core_wrapper/process_definitions => openeo_grass_gis_driver/actinia_processing}/zonal_statistics.py (87%) create mode 100644 src/openeo_grass_gis_driver/app.py create mode 100644 src/openeo_grass_gis_driver/capabilities.py create mode 100644 src/openeo_grass_gis_driver/data.py rename src/{graas_openeo_core_wrapper => openeo_grass_gis_driver}/data_product_id.py (54%) create mode 100644 src/openeo_grass_gis_driver/definitions.py rename src/{graas_openeo_core_wrapper => openeo_grass_gis_driver}/endpoints.py (59%) rename src/{graas_openeo_core_wrapper => openeo_grass_gis_driver}/graph_db.py (68%) rename src/{graas_openeo_core_wrapper => openeo_grass_gis_driver}/jobs.py (56%) rename src/{graas_openeo_core_wrapper => openeo_grass_gis_driver}/jobs_job_id.py (53%) rename src/{graas_openeo_core_wrapper => openeo_grass_gis_driver}/main.py (76%) create mode 100644 src/openeo_grass_gis_driver/processes.py create mode 100644 src/openeo_grass_gis_driver/processes_process_id.py rename src/{graas_openeo_core_wrapper => openeo_grass_gis_driver}/test_base.py (66%) create mode 100644 src/openeo_grass_gis_driver/udf.py create mode 100644 src/openeo_grass_gis_driver/udf_lang_udf_type.py diff --git a/.coveragerc b/.coveragerc index 09887768..b80794c9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,7 +1,7 @@ # .coveragerc to control coverage.py [run] branch = True -source = */graas_openeo_core_wrapper/* +source = */openeo_grass_gis_driver/* # omit = bad_file.py [report] diff --git a/docs/conf.py b/docs/conf.py index 5e8acf1f..0758f3b1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,7 +32,7 @@ from sphinx import apidoc output_dir = os.path.join(__location__, "api") -module_dir = os.path.join(__location__, "../src/graas_openeo_core_wrapper") +module_dir = os.path.join(__location__, "../src/openeo_grass_gis_driver") try: shutil.rmtree(output_dir) except FileNotFoundError: @@ -66,7 +66,7 @@ master_doc = 'index' # General information about the project. -project = u'graas_openeo_core_wrapper' +project = u'openeo_grass_gis_driver' copyright = u'2018, Soeren Gebbert' # The version info for the project you're documenting, acts as replacement for @@ -198,7 +198,7 @@ # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'graas_openeo_core_wrapper-doc' +htmlhelp_basename = 'openeo_grass_gis_driver-doc' # -- Options for LaTeX output -------------------------------------------------- @@ -217,7 +217,7 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'user_guide.tex', u'graas_openeo_core_wrapper Documentation', + ('index', 'user_guide.tex', u'openeo_grass_gis_driver Documentation', u'Soeren Gebbert', 'manual'), ] diff --git a/requirements.txt b/requirements.txt index 0d4ed0af..2b9f8a09 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,32 @@ # Example: # numpy==1.13.3 # scipy==1.0 +alabaster==0.7.10 +aniso8601==2.0.0 +Babel==2.5.3 +certifi==2018.1.18 +chardet==3.0.4 +click==6.7 +docutils==0.14 +Flask==0.12.2 +Flask-Cors==3.0.3 +Flask-RESTful==0.3.6 +flask-restful-swagger-2==0.35 +idna==2.6 +imagesize==0.7.1 +itsdangerous==0.24 +Jinja2==2.10 +MarkupSafe==1.0 +Pygments==2.2.0 +PyScaffold==3.0 +pytz==2017.3 +requests==2.18.4 +six==1.11.0 +snowballstemmer==1.2.1 +Sphinx==1.6.6 +sphinxcontrib-websupport==1.0.1 +urllib3==1.22 +uWSGI==2.0.15 +Werkzeug==0.14.1 +sqlitedict==1.5.0 diff --git a/setup.cfg b/setup.cfg index d7d807de..2a513622 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ # http://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files [metadata] -name = graas_openeo_core_wrapper +name = openeo_grass_gis_driver description = Add a short description here! author = Soeren Gebbert author-email = soerengebbert@googlemail.com @@ -37,7 +37,7 @@ exclude = [options.extras_require] # Add here additional requirements for extra features, to install with: -# `pip install graas_openeo_core_wrapper[PDF]` like: +# `pip install openeo_grass_gis_driver[PDF]` like: # PDF = ReportLab; RXP [test] @@ -50,7 +50,7 @@ addopts = tests # e.g. --cov-report html (or xml) for html/xml output or --junitxml junit.xml # in order to write a coverage file that can be read by Jenkins. addopts = - --cov graas_openeo_core_wrapper --cov-report term-missing + --cov openeo_grass_gis_driver --cov-report term-missing --verbose norecursedirs = dist @@ -88,6 +88,6 @@ exclude = # PyScaffold's parameters when the project was created. # This will be used when updating. Do not change! version = 3.0 -package = graas_openeo_core_wrapper +package = openeo_grass_gis_driver extensions = no_skeleton diff --git a/setup.py b/setup.py index 237fae07..76080605 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ - Setup file for graas_openeo_core_wrapper. + Setup file for openeo_grass_gis_driver. This file was generated with PyScaffold 3.0. PyScaffold helps you to put up the scaffold of your new Python project. @@ -14,9 +14,9 @@ # Add here console scripts and other entry points in ini-style format entry_points = """ [console_scripts] -# script_name = graas_openeo_core_wrapper.module:function +# script_name = openeo_grass_gis_driver.module:function # For example: -# fibonacci = graas_openeo_core_wrapper.skeleton:run +# fibonacci = openeo_grass_gis_driver.skeleton:run """ diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/graas_openeo_core_wrapper/capabilities.py b/src/graas_openeo_core_wrapper/capabilities.py deleted file mode 100644 index 0e7ef4db..00000000 --- a/src/graas_openeo_core_wrapper/capabilities.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -from openeo_core.capabilities import Capabilities, GET_CAPABILITIES_DOC -from flask import make_response, jsonify -from flask_restful_swagger_2 import swagger - -__license__ = "Apache License, Version 2.0" -__author__ = "Sören Gebbert" -__copyright__ = "Copyright 2018, Sören Gebbert" -__maintainer__ = "Soeren Gebbert" -__email__ = "soerengebbert@googlemail.com" - - -GRAAS_CAPABILITIES=["/capabilities", - "/data", - "/data/{product_id}", - '/processes', - '/processes/{process_id}', - '/jobs', - '/jobs/job_id', - '/udf', - '/udf/{lang}/{udf_type}'] - - -class GRaaSCapabilities(Capabilities): - - @swagger.doc(GET_CAPABILITIES_DOC) - def get(self, ): - return make_response(jsonify(GRAAS_CAPABILITIES), 200) diff --git a/src/graas_openeo_core_wrapper/data.py b/src/graas_openeo_core_wrapper/data.py deleted file mode 100644 index 0662570a..00000000 --- a/src/graas_openeo_core_wrapper/data.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -from openeo_core.data import Data, GET_DATA_DOC -from openeo_core.definitions import DataSetListEntry, DataSetInfo -from graas_openeo_core_wrapper.graas_interface import GRaaSInterface -from flask import make_response, jsonify -from flask_restful_swagger_2 import swagger -from graas_openeo_core_wrapper.config import Config - -__license__ = "Apache License, Version 2.0" -__author__ = "Sören Gebbert" -__copyright__ = "Copyright 2018, Sören Gebbert" -__maintainer__ = "Soeren Gebbert" -__email__ = "soerengebbert@googlemail.com" - - -class GRaaSData(Data): - - def __init__(self): - self.iface = GRaaSInterface() - - @swagger.doc(GET_DATA_DOC) - def get(self, ): - - dataset_list = [] - - for location in Config.LOCATIONS: - - status_code, mapsets = self.iface.list_mapsets(location=location) - if status_code != 200: - return make_response(jsonify({"description":"An internal error occurred " - "while catching mapset " - "from location %s!"%location}, 400)) - - for mapset in mapsets: - - # List strds maps from the GRASS location - status_code, strds_data = self.iface.list_strds(location=location, mapset=mapset) - if status_code != 200: - return make_response(jsonify({"description":"An internal error occurred " - "while catching strds layers!"}, 400)) - - for entry in strds_data: - strds_id = "%s.%s.strds.%s"%(location, mapset, entry) - ds = DataSetListEntry(product_id=strds_id, description="Space time raster dataset", - source="GRASS GIS location/mapset path: " - "/%s/%s"%(location, mapset)) - dataset_list.append(ds) - - # List raster maps from the GRASS location - status_code, raster_data = self.iface.list_raster(location=location, mapset=mapset) - if status_code != 200: - return make_response(jsonify({"description":"An internal error occurred " - "while catching strds layers!"}, 400)) - - for entry in raster_data: - raster_id = "%s.%s.raster.%s"%(location, mapset, entry) - ds = DataSetListEntry(product_id=raster_id, description="Raster dataset", - source="GRASS GIS location/mapset path: " - "/%s/%s"%(location, mapset)) - dataset_list.append(ds) - - return make_response(jsonify(dataset_list), 200) diff --git a/src/graas_openeo_core_wrapper/processes.py b/src/graas_openeo_core_wrapper/processes.py deleted file mode 100644 index 12e291ee..00000000 --- a/src/graas_openeo_core_wrapper/processes.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -from openeo_core.processes import Processes, GET_PROCESSES_DOC -from flask import make_response, jsonify -from flask_restful_swagger_2 import swagger -from graas_openeo_core_wrapper import process_definitions - -__license__ = "Apache License, Version 2.0" -__author__ = "Sören Gebbert" -__copyright__ = "Copyright 20186, Sören Gebbert" -__maintainer__ = "Soeren Gebbert" -__email__ = "soerengebbert@googlemail.com" - - -class GRaaSProcesses(Processes): - - @swagger.doc(GET_PROCESSES_DOC) - def get(self): - return make_response(jsonify(list(process_definitions.PROCESS_DESCRIPTION_DICT.keys())), 200) diff --git a/src/graas_openeo_core_wrapper/processes_process_id.py b/src/graas_openeo_core_wrapper/processes_process_id.py deleted file mode 100644 index 4fbb4515..00000000 --- a/src/graas_openeo_core_wrapper/processes_process_id.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -from flask import make_response, jsonify -from flask_restful_swagger_2 import swagger -from openeo_core.processes_process_id import ProcessesProcessId, GET_PROCESSES_PROCESS_ID_DOC -from graas_openeo_core_wrapper import process_definitions - -__license__ = "Apache License, Version 2.0" -__author__ = "Sören Gebbert" -__copyright__ = "Copyright 20186, Sören Gebbert" -__maintainer__ = "Soeren Gebbert" -__email__ = "soerengebbert@googlemail.com" - - -class GRaaSProcessesProcessId(ProcessesProcessId): - - @swagger.doc(GET_PROCESSES_PROCESS_ID_DOC) - def get(self, process_id): - - if process_id not in process_definitions.PROCESS_DESCRIPTION_DICT: - return make_response(jsonify({"description": "This process does not exists!"}), 400) - - return make_response(jsonify(process_definitions.PROCESS_DESCRIPTION_DICT[process_id]), 200) diff --git a/src/graas_openeo_core_wrapper/udf.py b/src/graas_openeo_core_wrapper/udf.py deleted file mode 100644 index a8811941..00000000 --- a/src/graas_openeo_core_wrapper/udf.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -from openeo_core.udf import GET_UDF_DOC -from openeo_core.udf import Udf -from flask import make_response, jsonify -from flask_restful_swagger_2 import swagger - -__license__ = "Apache License, Version 2.0" -__author__ = "Sören Gebbert" -__copyright__ = "Copyright 20186, Sören Gebbert" -__maintainer__ = "Soeren Gebbert" -__email__ = "soerengebbert@googlemail.com" - - -SUPPORTED_UDF = { - "python": { - "udf_types": [ - "reduce_time" - ], - "versions": { - "2.7": { - "packages": [ - "numpy", - "scipy" - ] - } - } - } -} - - -class GRaaSUdf(Udf): - - @swagger.doc(GET_UDF_DOC) - def get(self): - return make_response(jsonify(SUPPORTED_UDF), 200) diff --git a/src/graas_openeo_core_wrapper/udf_lang_udf_type.py b/src/graas_openeo_core_wrapper/udf_lang_udf_type.py deleted file mode 100644 index d2dbb895..00000000 --- a/src/graas_openeo_core_wrapper/udf_lang_udf_type.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -import pprint -from openeo_core.jobs import POST_JOBS_DOC -from openeo_core.jobs import Jobs -from graas_openeo_core_wrapper.graas_interface import GRaaSInterface -from flask import make_response, jsonify, request -from flask_restful_swagger_2 import swagger -from graas_openeo_core_wrapper.graph_db import GraphDB -from graas_openeo_core_wrapper.process_definitions import udf_reduce_time - -__license__ = "Apache License, Version 2.0" -__author__ = "Sören Gebbert" -__copyright__ = "Copyright 20186, Sören Gebbert" -__maintainer__ = "Soeren Gebbert" -__email__ = "soerengebbert@googlemail.com" - - -python_udfs = dict(python={}) -python_udfs["python"][udf_reduce_time.PROCESS_NAME] = udf_reduce_time.DOC - - -class GRaaSUdfType(Jobs): - - def __init__(self): - self.iface = GRaaSInterface() - self.db = GraphDB() - - @swagger.doc(POST_JOBS_DOC) - def get(self, lang, udf_type): - - if lang not in python_udfs: - return make_response(jsonify({"description": "UDF type with specified identifier is not available"}), 404) - - if udf_type not in python_udfs[lang]: - return make_response(jsonify({"description": "UDF type with specified identifier is not available"}), 404) - - return make_response(jsonify(python_udfs[lang][udf_type]), 200) diff --git a/src/graas_openeo_core_wrapper/__init__.py b/src/openeo_grass_gis_driver/__init__.py similarity index 100% rename from src/graas_openeo_core_wrapper/__init__.py rename to src/openeo_grass_gis_driver/__init__.py diff --git a/src/graas_openeo_core_wrapper/process_definitions/__init__.py b/src/openeo_grass_gis_driver/actinia_processing/__init__.py similarity index 76% rename from src/graas_openeo_core_wrapper/process_definitions/__init__.py rename to src/openeo_grass_gis_driver/actinia_processing/__init__.py index 31f70fd5..5e21781d 100644 --- a/src/graas_openeo_core_wrapper/process_definitions/__init__.py +++ b/src/openeo_grass_gis_driver/actinia_processing/__init__.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- -# This is the process dictionary that is used to store all processes of the GRaaS wrapper +# This is the process dictionary that is used to store all processes of the Actinia wrapper PROCESS_DESCRIPTION_DICT = {} PROCESS_DICT = {} -# Import the process_definitions to fill the process.PROCESS_DICT with process_definitions -import graas_openeo_core_wrapper.process_definitions.filter_bbox_process -import graas_openeo_core_wrapper.process_definitions.filter_daterange_process -import graas_openeo_core_wrapper.process_definitions.ndvi_process -import graas_openeo_core_wrapper.process_definitions.min_time_process -import graas_openeo_core_wrapper.process_definitions.udf_reduce_time -import graas_openeo_core_wrapper.process_definitions.raster_exporter -import graas_openeo_core_wrapper.process_definitions.zonal_statistics +# Import the actinia_processing to fill the process.PROCESS_DICT with actinia_processing +from . import filter_bbox_process +from . import filter_daterange_process +from . import ndvi_process +from . import min_time_process +from . import udf_reduce_time +from . import raster_exporter +from . import zonal_statistics __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" diff --git a/src/graas_openeo_core_wrapper/graas_interface.py b/src/openeo_grass_gis_driver/actinia_processing/actinia_interface.py similarity index 96% rename from src/graas_openeo_core_wrapper/graas_interface.py rename to src/openeo_grass_gis_driver/actinia_processing/actinia_interface.py index a2ebf182..b5c43717 100644 --- a/src/graas_openeo_core_wrapper/graas_interface.py +++ b/src/openeo_grass_gis_driver/actinia_processing/actinia_interface.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from flask import json -from graas_openeo_core_wrapper.config import Config as GRaaSConfig -import graas_openeo_core_wrapper +from .config import Config as ActiniaConfig import requests __license__ = "Apache License, Version 2.0" @@ -11,12 +9,14 @@ __email__ = "soerengebbert@googlemail.com" -class GRaaSInterface(object): +class ActiniaInterface(object): + + PROCESS_LOCATION = {} def __init__(self, config=None): if config is None: - config = GRaaSConfig + config = ActiniaConfig self.host = config.HOST self.port = config.PORT @@ -42,7 +42,7 @@ def layer_def_to_components(layer): location, mapset, datatype, layer = layer.split(".", 3) # Store the location in the global location dict - graas_openeo_core_wrapper.PROCESS_LOCATION[location] = location + ActiniaInterface.PROCESS_LOCATION[location] = location return location, mapset, datatype, layer @@ -162,7 +162,7 @@ def layer_info(self, layer_name): :param layer_name: :return: """ - location, mapset, datatype, layer = GRaaSInterface.layer_def_to_components(layer_name) + location, mapset, datatype, layer = ActiniaInterface.layer_def_to_components(layer_name) if datatype == "raster": datatype = "raster_layers" if datatype == "vector": diff --git a/src/graas_openeo_core_wrapper/config.py b/src/openeo_grass_gis_driver/actinia_processing/config.py similarity index 79% rename from src/graas_openeo_core_wrapper/config.py rename to src/openeo_grass_gis_driver/actinia_processing/config.py index 596a1884..dfd59c56 100644 --- a/src/graas_openeo_core_wrapper/config.py +++ b/src/openeo_grass_gis_driver/actinia_processing/config.py @@ -9,13 +9,12 @@ class Config(object): # Settings for docker swarm image - # HOST="http://graas" - HOST="http://openeo.mundialis.de" - PORT=8080 + HOST="http://actinia.mundialis.de" + PORT=433 LOCATION="ECAD" # LOCATION="LL" LOCATIONS=["LL", "ECAD"] - USER="user" - PASSWORD="abcdefgh" + USER="demouser" + PASSWORD="gu3st!pa55w0rd" # The database file that stores the graphs GRAPH_DB="%s/.graph_db_file.sqlite"%os.environ["HOME"] diff --git a/src/graas_openeo_core_wrapper/process_definitions/filter_bbox_process.py b/src/openeo_grass_gis_driver/actinia_processing/filter_bbox_process.py similarity index 85% rename from src/graas_openeo_core_wrapper/process_definitions/filter_bbox_process.py rename to src/openeo_grass_gis_driver/actinia_processing/filter_bbox_process.py index 8f026b91..f10d2dff 100644 --- a/src/graas_openeo_core_wrapper/process_definitions/filter_bbox_process.py +++ b/src/openeo_grass_gis_driver/actinia_processing/filter_bbox_process.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from graas_openeo_core_wrapper import process_definitions -from graas_openeo_core_wrapper.graas_interface import GRaaSInterface from random import randint +from . import analyse_process_graph, PROCESS_DICT, PROCESS_DESCRIPTION_DICT __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" @@ -49,10 +48,10 @@ } } -process_definitions.PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC +PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC -def create_graas_process_chain_entry(left, right, top, bottom, ewres, nsres): +def create_process_chain_entry(left, right, top, bottom, ewres, nsres): """Create a GRaaS command of the process chain that uses g.region to create a valid computational region for the provide input strds @@ -89,7 +88,7 @@ def get_process_list(args): :return: (output_name, pc) """ - input_names, process_list = process_definitions.analyse_process_graph(args) + input_names, process_list = analyse_process_graph(args) output_names = [] for input_name in input_names: @@ -108,10 +107,10 @@ def get_process_list(args): if "srs" in args: print("SRS is currently not supported") - pc = create_graas_process_chain_entry(left=left, right=right, top=top, bottom=bottom, ewres=ewres, nsres=nsres) + pc = create_process_chain_entry(left=left, right=right, top=top, bottom=bottom, ewres=ewres, nsres=nsres) process_list.append(pc) return output_names, process_list -process_definitions.PROCESS_DICT[PROCESS_NAME] = get_process_list +PROCESS_DICT[PROCESS_NAME] = get_process_list diff --git a/src/graas_openeo_core_wrapper/process_definitions/filter_daterange_process.py b/src/openeo_grass_gis_driver/actinia_processing/filter_daterange_process.py similarity index 74% rename from src/graas_openeo_core_wrapper/process_definitions/filter_daterange_process.py rename to src/openeo_grass_gis_driver/actinia_processing/filter_daterange_process.py index 72f9930c..9548d9fc 100644 --- a/src/graas_openeo_core_wrapper/process_definitions/filter_daterange_process.py +++ b/src/openeo_grass_gis_driver/actinia_processing/filter_daterange_process.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from random import randint -from graas_openeo_core_wrapper import process_definitions -from graas_openeo_core_wrapper.graas_interface import GRaaSInterface +from . import analyse_process_graph, PROCESS_DICT, PROCESS_DESCRIPTION_DICT +from .actinia_interface import ActiniaInterface __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" @@ -29,10 +29,10 @@ } } -process_definitions.PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC +PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC -def create_graas_process_chain_entry(input_name, start_time, end_time, output_name): +def create__process_chain_entry(input_name, start_time, end_time, output_name): """Create a GRaaS command of the process chain that uses t.rast.extract to create a subset of a strds :param strds_name: The name of the strds @@ -40,7 +40,7 @@ def create_graas_process_chain_entry(input_name, start_time, end_time, output_na :param end_time: :return: A GRaaS process chain description """ - location, mapset, datatype, layer_name = GRaaSInterface.layer_def_to_components(input_name) + location, mapset, datatype, layer_name = ActiniaInterface.layer_def_to_components(input_name) input_name = layer_name if mapset is not None: input_name = layer_name + "@" + mapset @@ -72,12 +72,12 @@ def get_process_list(args): """ # Get the input description and the process chain to attach this process - input_names, process_list = process_definitions.analyse_process_graph(args) + input_names, process_list = analyse_process_graph(args) output_names = [] for input_name in input_names: - location, mapset, datatype, layer_name = GRaaSInterface.layer_def_to_components(input_name) + location, mapset, datatype, layer_name = ActiniaInterface.layer_def_to_components(input_name) output_name = "%s_%s" % (layer_name, PROCESS_NAME) output_names.append(output_name) @@ -89,13 +89,13 @@ def get_process_list(args): if "to" in args: end_time = args["to"] - pc = create_graas_process_chain_entry(input_name=input_name, - start_time=start_time, - end_time=end_time, - output_name=output_name) + pc = create__process_chain_entry(input_name=input_name, + start_time=start_time, + end_time=end_time, + output_name=output_name) process_list.append(pc) return output_names, process_list -process_definitions.PROCESS_DICT[PROCESS_NAME] = get_process_list +PROCESS_DICT[PROCESS_NAME] = get_process_list diff --git a/src/graas_openeo_core_wrapper/process_definitions/min_time_process.py b/src/openeo_grass_gis_driver/actinia_processing/min_time_process.py similarity index 73% rename from src/graas_openeo_core_wrapper/process_definitions/min_time_process.py rename to src/openeo_grass_gis_driver/actinia_processing/min_time_process.py index a8fe2ffc..5228d21b 100644 --- a/src/graas_openeo_core_wrapper/process_definitions/min_time_process.py +++ b/src/openeo_grass_gis_driver/actinia_processing/min_time_process.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- from random import randint -from graas_openeo_core_wrapper import process_definitions -from graas_openeo_core_wrapper.graas_interface import GRaaSInterface +from . import analyse_process_graph, PROCESS_DICT, PROCESS_DESCRIPTION_DICT +from .actinia_interface import ActiniaInterface + __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" @@ -22,10 +23,10 @@ } } -process_definitions.PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC +PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC -def create_graas_process_chain_entry(input_name, output_name): +def create_process_chain_entry(input_name, output_name): """Create a GRaaS process description that uses t.rast.series to create the minimum value of the time series. @@ -34,7 +35,7 @@ def create_graas_process_chain_entry(input_name, output_name): :return: A GRaaS process chain description """ - location, mapset, datatype, layer_name = GRaaSInterface.layer_def_to_components(input_name) + location, mapset, datatype, layer_name = ActiniaInterface.layer_def_to_components(input_name) input_name = layer_name if mapset is not None: input_name = layer_name + "@" + mapset @@ -58,19 +59,19 @@ def get_process_list(args): :param args: The process description arguments :return: (output_name, pc) """ - input_names, process_list = process_definitions.analyse_process_graph(args) + input_names, process_list = analyse_process_graph(args) output_names = [] for input_name in input_names: - location, mapset, datatype, layer_name = GRaaSInterface.layer_def_to_components(input_name) + location, mapset, datatype, layer_name = ActiniaInterface.layer_def_to_components(input_name) output_name = "%s_%s" % (layer_name, PROCESS_NAME) output_names.append(output_name) - pc = create_graas_process_chain_entry(input_name, + pc = create_process_chain_entry(input_name, output_name) process_list.append(pc) return output_names, process_list -process_definitions.PROCESS_DICT[PROCESS_NAME] = get_process_list +PROCESS_DICT[PROCESS_NAME] = get_process_list diff --git a/src/graas_openeo_core_wrapper/process_definitions/ndvi_process.py b/src/openeo_grass_gis_driver/actinia_processing/ndvi_process.py similarity index 79% rename from src/graas_openeo_core_wrapper/process_definitions/ndvi_process.py rename to src/openeo_grass_gis_driver/actinia_processing/ndvi_process.py index 989af36d..6c2caba4 100644 --- a/src/graas_openeo_core_wrapper/process_definitions/ndvi_process.py +++ b/src/openeo_grass_gis_driver/actinia_processing/ndvi_process.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from random import randint -from graas_openeo_core_wrapper import process_definitions -from graas_openeo_core_wrapper.graas_interface import GRaaSInterface +from . import analyse_process_graph, PROCESS_DICT, PROCESS_DESCRIPTION_DICT +from .actinia_interface import ActiniaInterface __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" @@ -27,10 +27,10 @@ } } -process_definitions.PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC +PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC -def create_graas_process_chain_entry(nir_time_series, red_time_series, output_time_series): +def create_process_chain_entry(nir_time_series, red_time_series, output_time_series): """Create a GRaaS process description that uses t.rast.series to create the minimum value of the time series. @@ -39,17 +39,17 @@ def create_graas_process_chain_entry(nir_time_series, red_time_series, output_ti :param output_time_series: The name of the output time series :return: A list of GRaaS process chain descriptions """ - location, mapset, datatype, layer_name = GRaaSInterface.layer_def_to_components(nir_time_series) + location, mapset, datatype, layer_name = ActiniaInterface.layer_def_to_components(nir_time_series) nir_time_series = layer_name if mapset is not None: nir_time_series = layer_name + "@" + mapset - location, mapset, datatype, layer_name = GRaaSInterface.layer_def_to_components(red_time_series) + location, mapset, datatype, layer_name = ActiniaInterface.layer_def_to_components(red_time_series) red_time_series = layer_name if mapset is not None: red_time_series = layer_name + "@" + mapset - location, mapset, datatype, output_name = GRaaSInterface.layer_def_to_components(output_time_series) + location, mapset, datatype, output_name = ActiniaInterface.layer_def_to_components(output_time_series) rn = randint(0, 1000000) @@ -85,7 +85,7 @@ def get_process_list(args): :return: (output_time_series, pc) """ - input_names, process_list = process_definitions.analyse_process_graph(args) + input_names, process_list = analyse_process_graph(args) output_names = [] # Two input names are required @@ -108,14 +108,14 @@ def get_process_list(args): if nir_time_series is None or red_time_series is None: raise Exception("Band information is missing from process description") - location, mapset, datatype, layer_name = GRaaSInterface.layer_def_to_components(nir_time_series) + location, mapset, datatype, layer_name = ActiniaInterface.layer_def_to_components(nir_time_series) output_name = "%s_%s" % (layer_name, PROCESS_NAME) output_names.append(output_name) - pc = create_graas_process_chain_entry(nir_time_series, red_time_series, output_name) + pc = create_process_chain_entry(nir_time_series, red_time_series, output_name) process_list.extend(pc) return output_names, process_list -process_definitions.PROCESS_DICT[PROCESS_NAME] = get_process_list +PROCESS_DICT[PROCESS_NAME] = get_process_list diff --git a/src/graas_openeo_core_wrapper/process_definitions/raster_exporter.py b/src/openeo_grass_gis_driver/actinia_processing/raster_exporter.py similarity index 77% rename from src/graas_openeo_core_wrapper/process_definitions/raster_exporter.py rename to src/openeo_grass_gis_driver/actinia_processing/raster_exporter.py index b8526e7c..3c4e4e16 100644 --- a/src/graas_openeo_core_wrapper/process_definitions/raster_exporter.py +++ b/src/openeo_grass_gis_driver/actinia_processing/raster_exporter.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -from graas_openeo_core_wrapper import process_definitions -from graas_openeo_core_wrapper.graas_interface import GRaaSInterface from random import randint +from . import analyse_process_graph, PROCESS_DICT, PROCESS_DESCRIPTION_DICT +from .actinia_interface import ActiniaInterface + __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" @@ -21,10 +22,10 @@ } } -process_definitions.PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC +PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC -def create_graas_process_chain_entry(input_name): +def create_process_chain_entry(input_name): """Create a GRaaS command of the process chain that computes the regional statistics based on a strds and a polygon. @@ -32,7 +33,7 @@ def create_graas_process_chain_entry(input_name): :return: A GRaaS process chain description """ - location, mapset, datatype, layer_name = GRaaSInterface.layer_def_to_components(input_name) + location, mapset, datatype, layer_name = ActiniaInterface.layer_def_to_components(input_name) input_name = layer_name if mapset is not None: input_name = layer_name + "@" + mapset @@ -61,7 +62,7 @@ def get_process_list(args): """ # Get the input description and the process chain to attach this process - input_names, process_list = process_definitions.analyse_process_graph(args) + input_names, process_list = analyse_process_graph(args) output_names = [] for input_name in input_names: @@ -69,7 +70,7 @@ def get_process_list(args): output_name = input_name output_names.append(output_name) - pc = create_graas_process_chain_entry(input_name=input_name) + pc = create_process_chain_entry(input_name=input_name) process_list.extend(pc) import pprint @@ -78,4 +79,4 @@ def get_process_list(args): return output_names, process_list -process_definitions.PROCESS_DICT[PROCESS_NAME] = get_process_list +PROCESS_DICT[PROCESS_NAME] = get_process_list diff --git a/src/graas_openeo_core_wrapper/process_definitions/udf_reduce_time.py b/src/openeo_grass_gis_driver/actinia_processing/udf_reduce_time.py similarity index 73% rename from src/graas_openeo_core_wrapper/process_definitions/udf_reduce_time.py rename to src/openeo_grass_gis_driver/actinia_processing/udf_reduce_time.py index 5db4251c..d15b5954 100644 --- a/src/graas_openeo_core_wrapper/process_definitions/udf_reduce_time.py +++ b/src/openeo_grass_gis_driver/actinia_processing/udf_reduce_time.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from graas_openeo_core_wrapper import process_definitions -from graas_openeo_core_wrapper.graas_interface import GRaaSInterface -from random import randint +from . import analyse_process_graph, PROCESS_DICT, PROCESS_DESCRIPTION_DICT +from .actinia_interface import ActiniaInterface __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" @@ -25,10 +24,10 @@ } } -process_definitions.PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC +PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC -def create_graas_process_chain_entry(input_name, python_file_url, output_name): +def create_process_chain_entry(input_name, python_file_url, output_name): """Create a GRaaS command of the process chain that uses g.region to create a valid computational region for the provide input strds @@ -38,7 +37,7 @@ def create_graas_process_chain_entry(input_name, python_file_url, output_name): :return: A GRaaS process chain description """ - location, mapset, datatype, layer_name = GRaaSInterface.layer_def_to_components(input_name) + location, mapset, datatype, layer_name = ActiniaInterface.layer_def_to_components(input_name) input_name = layer_name if mapset is not None: input_name = layer_name + "@" + mapset @@ -66,12 +65,12 @@ def get_process_list(args): """ # Get the input description and the process chain to attach this process - input_names, process_list = process_definitions.analyse_process_graph(args) + input_names, process_list = analyse_process_graph(args) output_names = [] for input_name in input_names: - location, mapset, datatype, layer_name = GRaaSInterface.layer_def_to_components(input_name) + location, mapset, datatype, layer_name = ActiniaInterface.layer_def_to_components(input_name) output_name = "%s_%s" % (layer_name, PROCESS_NAME) output_names.append(output_name) @@ -80,12 +79,12 @@ def get_process_list(args): else: raise Exception("Python fle is missing in the process description") - pc = create_graas_process_chain_entry(input_name=input_name, - python_file_url=python_file_url, - output_name=output_name) + pc = create_process_chain_entry(input_name=input_name, + python_file_url=python_file_url, + output_name=output_name) process_list.append(pc) return output_names, process_list -process_definitions.PROCESS_DICT[PROCESS_NAME] = get_process_list +PROCESS_DICT[PROCESS_NAME] = get_process_list diff --git a/src/graas_openeo_core_wrapper/process_definitions/zonal_statistics.py b/src/openeo_grass_gis_driver/actinia_processing/zonal_statistics.py similarity index 87% rename from src/graas_openeo_core_wrapper/process_definitions/zonal_statistics.py rename to src/openeo_grass_gis_driver/actinia_processing/zonal_statistics.py index 479b04df..8cff1300 100644 --- a/src/graas_openeo_core_wrapper/process_definitions/zonal_statistics.py +++ b/src/openeo_grass_gis_driver/actinia_processing/zonal_statistics.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from graas_openeo_core_wrapper import process_definitions -from graas_openeo_core_wrapper.graas_interface import GRaaSInterface from random import randint +from . import analyse_process_graph, PROCESS_DICT, PROCESS_DESCRIPTION_DICT +from .actinia_interface import ActiniaInterface __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" @@ -27,10 +27,10 @@ } } -process_definitions.PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC +PROCESS_DESCRIPTION_DICT[PROCESS_NAME] = DOC -def create_graas_process_chain_entry(input_name, regions): +def create_process_chain_entry(input_name, regions): """Create a GRaaS command of the process chain that computes the regional statistics based on a strds and a polygon. @@ -42,7 +42,7 @@ def create_graas_process_chain_entry(input_name, regions): :return: A GRaaS process chain description """ - location, mapset, datatype, layer_name = GRaaSInterface.layer_def_to_components(input_name) + location, mapset, datatype, layer_name = ActiniaInterface.layer_def_to_components(input_name) input_name = layer_name if mapset is not None: input_name = layer_name + "@" + mapset @@ -124,7 +124,7 @@ def get_process_list(args): """ # Get the input description and the process chain to attach this process - input_names, process_list = process_definitions.analyse_process_graph(args) + input_names, process_list = analyse_process_graph(args) output_names = [] for input_name in input_names: @@ -137,11 +137,11 @@ def get_process_list(args): else: raise Exception("The vector polygon is missing in the process description") - pc = create_graas_process_chain_entry(input_name=input_name, + pc = create_process_chain_entry(input_name=input_name, regions=regions) process_list.extend(pc) return output_names, process_list -process_definitions.PROCESS_DICT[PROCESS_NAME] = get_process_list +PROCESS_DICT[PROCESS_NAME] = get_process_list diff --git a/src/openeo_grass_gis_driver/app.py b/src/openeo_grass_gis_driver/app.py new file mode 100644 index 00000000..60505ffd --- /dev/null +++ b/src/openeo_grass_gis_driver/app.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +from flask import Flask +from flask_cors import CORS +from flask_restful import Api + +__author__ = "Sören Gebbert" +__copyright__ = "Copyright 2018, Sören Gebbert" +__maintainer__ = "Soeren Gebbert" +__email__ = "soerengebbert@googlemail.com" + +flask_app = Flask(__name__) +CORS(flask_app) +flask_api = Api(flask_app) diff --git a/src/openeo_grass_gis_driver/capabilities.py b/src/openeo_grass_gis_driver/capabilities.py new file mode 100644 index 00000000..af121c0c --- /dev/null +++ b/src/openeo_grass_gis_driver/capabilities.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +from flask import make_response, jsonify +from flask_restful import Resource + +__license__ = "Apache License, Version 2.0" +__author__ = "Sören Gebbert" +__copyright__ = "Copyright 2018, Sören Gebbert" +__maintainer__ = "Soeren Gebbert" +__email__ = "soerengebbert@googlemail.com" + + +ACTINIA_CAPABILITIES=["/capabilities", + "/data", + "/data/{product_id}", + '/processes', + '/processes/{process_id}', + '/jobs', + '/jobs/job_id', + '/udf', + '/udf/{lang}/{udf_type}'] + + +GET_CAPABILITIES_DOC = { + "summary": "Returns the capabilities, i.e., which OpenEO API features are supported by the back-end.", + "description": "The request will ask the back-end which features of the OpenEO API are supported and " + "return a simple JSON description with available endpoints.", + "tags": ["API Information"], + "responses": { + "200": { + "description": "An array of implemented API endpoints", + "schema": { + "example": ["/data", "/data/{product_id}", "/processes"] + } + } + } +} + + +class Capabilities(Resource): + + def get(self, ): + return make_response(jsonify(ACTINIA_CAPABILITIES), 200) diff --git a/src/openeo_grass_gis_driver/data.py b/src/openeo_grass_gis_driver/data.py new file mode 100644 index 00000000..b6a8c54b --- /dev/null +++ b/src/openeo_grass_gis_driver/data.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +from .definitions import DataSetListEntry +from .actinia_processing.actinia_interface import ActiniaInterface +from flask import make_response, jsonify +from flask_restful import Resource +from .actinia_processing.config import Config + +__license__ = "Apache License, Version 2.0" +__author__ = "Sören Gebbert" +__copyright__ = "Copyright 2018, Sören Gebbert" +__maintainer__ = "Soeren Gebbert" +__email__ = "soerengebbert@googlemail.com" + +GET_DATA_EXAMPLE = [ + { + "product_id": "MOD09Q1", + "description": " MODIS/Terra Surface Reflectance 8-Day L3 Global 250m SIN Grid V006", + "source": "U.S. Geological Survey (USGS), DOI: 10.5067/MODIS/MOD09Q1.006" + }, + { + "product_id": "SENTINEL2-1C", + "description": "Sentinel 2 Level-1C: Top-of-atmosphere reflectances in cartographic geometry", + "source": "European Space Agency (ESA)" + }, + { + "product_id": "LandsatETM+", + "description": "Landsat Enhanced Thematic Mapper Plus (ETM+)", + "source": "U.S. Geological Survey (USGS)" + } +] + +GET_DATA_DOC = { + "summary": "Returns basic information about EO datasets that are available at the back-end and " + "offers simple search by time, space, and product name.", + "description": "Requests will ask the back-end for available data and will return an array of available datasets " + "with very basic information such as their unique identifiers. Results can be filtered by space, " + "time, and product name with very simple search expressions.", + "tags": ["EO Data Discovery"], + "parameters": [ + { + "in": "query", + "name": "qname", + "type": "string", + "description": "string expression to search available datasets by name", + "required": False + }, + { + "in": "query", + "name": "qgeom", + "type": "string", + "description": "WKT polygon to search for available datasets that spatially intersect with the polygon", + "required": False + }, + { + "in": "query", + "name": "qstartdate", + "type": "string", + "description": "ISO 8601 date/time string to find datasets with any data acquired after the given date/time", + "required": False + }, + { + "in": "query", + "name": "qenddate", + "type": "string", + "description": "ISO 8601 date/time string to find datasets with any data acquired before the given date/time", + "required": False + } + ], + "responses": { + "200": { + "description": "An array of EO datasets including their unique identifiers and some basic metadata.", + "schema": {"type": "array", + "items": DataSetListEntry + }, + "examples": {"application/json": GET_DATA_EXAMPLE} + }, + "401": {"$ref": "#/responses/auth_required"}, + "501": {"$ref": "#/responses/not_implemented"}, + "503": {"$ref": "#/responses/unavailable"} + } +} + +class Data(Resource): + + def __init__(self): + self.iface = ActiniaInterface() + + def get(self, ): + + dataset_list = [] + + for location in Config.LOCATIONS: + + status_code, mapsets = self.iface.list_mapsets(location=location) + if status_code != 200: + return make_response(jsonify({"description":"An internal error occurred " + "while catching mapset " + "from location %s!"%location}, 400)) + + for mapset in mapsets: + + # List strds maps from the GRASS location + status_code, strds_data = self.iface.list_strds(location=location, mapset=mapset) + if status_code != 200: + return make_response(jsonify({"description":"An internal error occurred " + "while catching strds layers!"}, 400)) + + for entry in strds_data: + strds_id = "%s.%s.strds.%s"%(location, mapset, entry) + ds = DataSetListEntry(product_id=strds_id, description="Space time raster dataset", + source="GRASS GIS location/mapset path: " + "/%s/%s"%(location, mapset)) + dataset_list.append(ds) + + # List raster maps from the GRASS location + status_code, raster_data = self.iface.list_raster(location=location, mapset=mapset) + if status_code != 200: + return make_response(jsonify({"description":"An internal error occurred " + "while catching strds layers!"}, 400)) + + for entry in raster_data: + raster_id = "%s.%s.raster.%s"%(location, mapset, entry) + ds = DataSetListEntry(product_id=raster_id, description="Raster dataset", + source="GRASS GIS location/mapset path: " + "/%s/%s"%(location, mapset)) + dataset_list.append(ds) + + return make_response(jsonify(dataset_list), 200) diff --git a/src/graas_openeo_core_wrapper/data_product_id.py b/src/openeo_grass_gis_driver/data_product_id.py similarity index 54% rename from src/graas_openeo_core_wrapper/data_product_id.py rename to src/openeo_grass_gis_driver/data_product_id.py index 8fb86ba4..c0f94aec 100644 --- a/src/graas_openeo_core_wrapper/data_product_id.py +++ b/src/openeo_grass_gis_driver/data_product_id.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -from openeo_core.data_product_id import DataProductId, GET_DATA_PRODUCT_ID_DOC -from openeo_core.definitions import SpatialExtent, DateTime, BandDescription -from graas_openeo_core_wrapper.graas_interface import GRaaSInterface from flask import make_response, jsonify -from flask_restful_swagger_2 import swagger +from flask_restful import Resource +from .definitions import SpatialExtent, DateTime, BandDescription +from .actinia_processing.actinia_interface import ActiniaInterface __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" @@ -75,12 +74,188 @@ "west": "630000" } -class GRaaSDataProductId(DataProductId): +GET_DATA_PRODUCT_ID_EXAMPLE = { + "product_id": "Sentinel-2A-L1C", + "description": "Sentinel 2 Level-1C: Top-of-atmosphere reflectances in cartographic geometry", + "source": "European Space Agency (ESA)", + "extent": { + "srs": "EPSG:4326", + "left": -34, + "right": 35, + "bottom": 39, + "top": 71 + }, + "time": {"from": "2016-01-01", "to": "2017-10-01"}, + "bands": [ + { + "band_id": "1", + "wavelength_nm": 443.9, + "res_m": 60, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + }, + { + "band_id": "2", + "name": "blue", + "wavelength_nm": 496.6, + "res_m": 10, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + }, + { + "band_id": "3", + "name": "green", + "wavelength_nm": 560, + "res_m": 10, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + }, + { + "band_id": "4", + "name": "red", + "wavelength_nm": 664.5, + "res_m": 10, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + }, + { + "band_id": "5", + "wavelength_nm": 703.9, + "res_m": 20, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + }, + { + "band_id": "6", + "wavelength_nm": 740.2, + "res_m": 20, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + }, + { + "band_id": "7", + "wavelength_nm": 782.5, + "res_m": 20, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + }, + { + "band_id": "8", + "name": "nir", + "wavelength_nm": 835.1, + "res_m": 10, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + }, + { + "band_id": "8a", + "wavelength_nm": 864.8, + "res_m": 20, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + }, + { + "band_id": "9", + "wavelength_nm": 945, + "res_m": 60, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + }, + { + "band_id": "10", + "wavelength_nm": 1373.5, + "res_m": 60, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + }, + { + "band_id": "11", + "wavelength_nm": 1613.7, + "res_m": 20, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + }, + { + "band_id": "12", + "wavelength_nm": 2202.4, + "res_m": 20, + "scale": 0.0001, + "offset": 0, + "type": "int16", + "unit": "1" + } + ] +} + +GET_DATA_PRODUCT_ID_DOC = { + "summary": "Returns further information on a given EO product available at the back-end.", + "description": "The request will ask the back-end for further details about a product " + "specified by the identifier `product_id`.", + "tags": ["EO Data Discovery"], + "parameters": [ + { + "name": "product_id", + "in": "path", + "type": "string", + "description": "product identifier string such as `MOD18Q1`", + "required": True + } + ], + "responses": { + "200": { + "description": "JSON object with metadata of the EO dataset.", + "schema": { + "type": "object", + "required": ["product_id", "description", "extent", "bands"], + "properties": { + "product_id": {"type": "string"}, + "description": {"type": "string"}, + "source": {"type": "string"}, + "extent": SpatialExtent, + "time": DateTime, + "bands": {"type": "array", "items": BandDataTypes} + }, + "additionalProperties": True + }, + "examples": {"application/json": GET_DATA_PRODUCT_ID_EXAMPLE} + }, + "401": {"$ref": "#/responses/auth_required"}, + "404": {"description": "EO dataset with specified identifier is not available"}, + "501": {"$ref": "#/responses/not_implemented"}, + "503": {"$ref": "#/responses/unavailable"} + } +} + + +class DataProductId(Resource): def __init__(self): - self.iface = GRaaSInterface() + self.iface = ActiniaInterface() - @swagger.doc(GET_DATA_PRODUCT_ID_DOC) def get(self, product_id): # List strds maps from the GRASS location diff --git a/src/openeo_grass_gis_driver/definitions.py b/src/openeo_grass_gis_driver/definitions.py new file mode 100644 index 00000000..8acd9055 --- /dev/null +++ b/src/openeo_grass_gis_driver/definitions.py @@ -0,0 +1,510 @@ +# -*- coding: utf-8 -*- +from flask_restful_swagger_2 import Schema + +__author__ = "Sören Gebbert" +__copyright__ = "Copyright 2018, Sören Gebbert" +__maintainer__ = "Sören Gebbert" +__email__ = "soerengebbert@googlemail.com" + + +class ProcessDescription(Schema): + description = "Defines and describes a process including it's expected input arguments." + type = "object" + required = ["process_id", "description"] + properties = { + "process_id": { + "type": "string", + "description": "The unique identifier of the process." + }, + "description": { + "type": "string", + "description": "A short and concise description of what the process does and how the output looks like." + }, + "link": { + "type": "string", + "description": "Reference to an external process definition if the process has been defined " + "over different back ends within OpenEO" + }, + "args": { + "type": "object", + "additionalProperties": { + "type": "object", + "required": ["description"], + "properties": { + "description": { + "type": "string", + "description": "A short and concise description of the process argument." + }, + "required": { + "type": "boolean", + "default": True, + "description": "Defines whether an argument is required or optional." + } + }, + "additionalProperties": True, + "description": "**DEFAULT VALUES FOR ARGUMENTS ARE NOT FORMALIZED IN THE SWAGGER 2.0 " + "DEFINITION DUE TO MISSING SUPPORT FOR oneOf OR anyOf SCHEMA COMBINATIONS.**" + } + } + } + example = { + "process_id": "band_arithmetic", + "description": "Perform basic arithmetic expressions on individual pixel and their band values.", + "args": { + "imagery": { + "description": "input image or image collection", + "required": True + }, + "expr": { + "description": "expressions as array, the result will have as many bands as the number " + "of given expressions." + } + } + } + + +##################################################################### + +class ProcessListEntry(Schema): + type = "object" + description = "A single entry of the process description list" + required = ["process_id", "description"] + properties = { + "process_id": {"type": "string"}, + "description": {"type": "string"} + } + example = { + "process_id": "NDVI", + "description": "Computes the normalized difference vegetation index (NDVI) for " + "all pixels of the input dataset." + } + additionalProperties = True + + +##################################################################### + +class SpatialExtent(Schema): + type = "object" + description = "spatial extent of the available imagery" + properties = { + "srs": { + "description": "spatial reference system readable by GDAL " + "(e.g. as 'WKT', 'proj4', or 'EPSG:xy')", + "type": "string" + }, + "left": {"type": "number"}, + "right": {"type": "number"}, + "top": {"type": "number"}, + "bottom": {"type": "number"} + } + + +##################################################################### + +class DateTime(Schema): + type = "object" + description = "Date, Time or datetime in ISO 8601 format" + properties = { + "from": { + "description": "Date/time in ISO 8601 format", + "type": "string", + "format": "date-time" + }, + "to": { + "description": "Date/time in ISO 8601 format", + "type": "string", + "format": "date-time" + } + } + + +##################################################################### + +class BandDataTypes(Schema): + type = "string" + description = "Data type for band values including its bit size." + enum = ["uint8", "uint16", "uint32", "uint64", "int8", "int16", "int32", + "int64", "float16", "float32", "float64"] + + +##################################################################### + +class BandDescription(Schema): + type = "object" + required = ["band_id"] + additionalProperties = True + properties = { + "band_id": { + "description": "unique identifier for bands", + "type": "string" + }, + "name": { + "description": "optional name to refer to bands by name such as 'red' instead of their band_id.", + "type": "string" + }, + "type": BandDataTypes, + "offset": { + "description": "offset to convert band values to the actual measurement scale.", + "type": "number", + "default": 0 + }, + "scale": { + "description": "scale to convert band values to the actual measurement scale.", + "type": "number", + "default": 1 + }, + "unit": { + "description": "unit of measurements (preferably SI)", + "type": "string" + }, + "nodata": { + "description": "specific values representing no data", + "type": "array", + "items": {"type": "number"} + } + } + + +##################################################################### + +class DataSetListEntry(Schema): + type = "object" + properties = { + "product_id": {"type": "string"}, + "description": {"type": "string"}, + "source": {"type": "string"} + } + additionalProperties = True + + +##################################################################### + +class DataSetInfo(Schema): + type = "object" + required = ["product_id", "description", "extent", "bands"] + properties = { + "product_id": {"type": "string"}, + "description": {"type": "string"}, + "source": {"type": "string"}, + "extent": SpatialExtent, + "time": DateTime, + "bands": {"type": "array", "items": BandDataTypes} + } + additionalProperties = True + + +##################################################################### + +class UDFTypen(Schema): + type = "string" + description = "The UDF types define how UDFs can be exposed to the data, how they can be parallelized, " \ + "and how the result schema should be structured." + enum = ["apply_pixel", "apply_scene", "reduce_time", "reduce_space", "window_time", "window_space", + "window_spacetime", "agregate_time", "aggregate_space", "aggregate_spacetime"] + + +##################################################################### + +class UDFDescription(Schema): + description = "Defines and describes a UDF using the same schema as the description of " \ + "processes offered by the back-end." + type = "object" + required = ["process_id", "description"] + properties = { + "process_id": { + "type": "string", + "description": "The unique identifier of the process." + }, + "description": { + "type": "string", + "description": "A short and concise description of what the process does and how the output looks like." + }, + "link": { + "type": "string", + "description": "Reference to an external process definition if the process has been " + "defined over different back ends within OpenEO" + }, + "args": { + "type": "object", + "additionalProperties": { + "type": "object", + "required": ["description"], + "properties": { + "description": { + "type": "string", + "description": "A short and concise description of the process argument." + }, + "required": { + "type": "boolean", + "default": True, + "description": "Defines whether an argument is required or optional." + } + }, + "additionalProperties": True, + "description": "**DEFAULT VALUES FOR ARGUMENTS ARE NOT FORMALIZED IN THE SWAGGER 2.0 " + "DEFINITION DUE TO MISSING SUPPORT FOR oneOf OR anyOf SCHEMA COMBINATIONS.**" + } + } + } + example = { + "process_id": "udf/R/reduce_time", + "description": "Applies an R function independently over all input time series that produces a " + "zero-dimensional value (scalar or multi-band tuple) as output (per time series).", + "args": { + "imagery": { + "description": "input (image) time series", + "required": True + }, + "script": { + "description": "Script resource that has been uploaded to user space before. ", + "required": True + } + } + } + + +##################################################################### + +class UserId(Schema): + description = "User id" + type = "string" + additionalProperties = True + + +##################################################################### + +class FileDescription(Schema): + description = "File description of user specific files" + type = "object" + required = ["name", "size", "modified"] + additionalProperties = True + properties = { + "name": { + "type": "string", + "description": "The name of the file." + }, + "size": { + "type": "integer", + "description": "The size of the file in bytes." + }, + "modified": { + "type": "string", + "description": "The creation/modification tim eof the file." + } + } + example = { + "name": "test.txt", + "size": 182, + "modified": "2015-10-20T17:22:10Z" + } + + +##################################################################### + +class ArgSet(Schema): + description = "Defines an object schema for collection (uniquely named) arguments as input to processes. " \ + "**THIS TYPE IS NOT FORMALIZED IN THE SWAGGER 2.0 DEFINITION DUE TO MISSING SUPPORT FOR oneOf " \ + "OR anyOf SCHEMA COMBINATIONS.**" + type = "object" + additionalProperties = True + + +##################################################################### + +class ProcessGraph(Schema): + description = "A process graph defines an executable process, i.e. one process or a combination of chained " \ + "processes including specific arguments." + type = "object" + required = ["process_id"] + properties = { + "process_id": { + "type": "string", + "description": "The unique identifier of the process." + }, + "args": { + "type": "array", + "items": ArgSet, + "description": "Collection of arguments identified by their name." + } + } + example = { + "process_id": "median_time", + "args": { + "imagery": { + "process_id": "NDVI", + "args": { + "imagery": { + "process_id": "filter_daterange", + "args": {"imagery": {"product_id": "Sentinel2A-L1C"}}, + "from": "2017-01-01", + "to": "2017-01-31" + } + }, + "red": "4", + "nir": "8" + } + } + } + + +##################################################################### + +class View(Schema): + description = "The view defines how we look at the data (spatial extent, resolution, time range, etc.) for " \ + "processing. It can be used to experiment with tasks and processes on small subdataset." + type = "object" + additionalProperties = True + properties = { + "space": { + "description": "Defines spatial resolution, window, and resampling method used for running " + "processes on small sub datasets", + "type": "object", + "properties": { + "srs": { + "type": "string", + "description": "Spatial reference system as proj4 string or epsg code such as `EPSG:3857`" + }, + "window": { + "type": "object", + "description": "boundaries of the spatial window as coordinates expressed in the " + "given reference system.", + "required": ["left", "top", "right", "bottom"], + "properties": { + "left": {"type": "number"}, + "top": {"type": "number"}, + "right": {"type": "number"}, + "bottom": {"type": "number"} + } + }, + "cell_size": {"type": "number"}, + "resampling": { + "type": "string", + "description": "resampling method to use (taken from [GDAL]" + "(http://www.gdal.org/gdal_translate.html))", + "default": "nearest", + "enum": ["nearest", "bilinear", "cubic", "cubicspline", "lanczos", "average", "mode"] + } + }, + "example": { + "srs": "EPSG:4326", + "window": { + "left": -10.21, + "top": 53.23, + "right": 12.542, + "bottom": 12.32 + }, + "resolution": 0.25, + "resampling": "nearest" + } + }, + "time": { + "description": "Defines temporal resolution, window, and resampling method used for running processes " + "on small sub datasets", + "type": "object", + "properties": { + "window": { + "type": "object", + "description": "Start and end date/time in ISO 8601 format", + "required": [ + "start", + "end" + ], + "properties": { + "start": { + "type": "string", + "format": "dateTime" + }, + "end": { + "type": "string", + "format": "dateTime" + } + } + }, + "time_step": { + "type": "string", + "description": "temporal granularity given as ISO 8601 time duration. In order to avoid mixing " + "inconsistent durations such as `P1M30DT24H` only a single integer number with " + "date/time component such as `P1M`, `P30D`, `PT24H` should be specified." + }, + "resampling": { + "type": "string", + "description": "resampling method to use " + "(taken from [GDAL](http://www.gdal.org/gdal_translate.html))", + "default": "nearest", + "enum": [ + "nearest", + "bilinear", + "cubic", + "cubicspline", + "lanczos", + "average", + "mode" + ] + } + }, + "example": { + "window": { + "start": "2017-01-01", + "end": "2018-01-01" + }, + "resolution": "P1M", + "resampling": "nearest" + } + } + } + + +##################################################################### + +class Job(Schema): + description = "Defines metadata of processing jobs that have been submitted by users." + type = "object" + required = ["job_id", "status", "process_graph", "user_id"] + additionalProperties = True + properties = { + "job_id": { + "type": "string", + "description": "Unique identifier of a job that is generated by the back-end during job submission." + }, + "status": { + "type": "string", + "enum": ["submitted", "running", "finished", "canceled", "error", "unknown", "waiting"], + "description": "The current status of the job." + }, + "process_graph": ProcessGraph, + "view": View, + "submitted": { + "type": "string", + "format": "dateTime", + "description": "Date and time of job submission in ISO 8601 format" + }, + "last_update": { + "type": "string", + "format": "dateTime", + "description": "Date and time of last status change in ISO 8601 format" + }, + "user_id": { + "type": "string", + "description": "Identifier of the user, who submitted the job and pays incurred costs if needed." + }, + "consumed_credits": { + "type": "number", + "description": "Credits consumed by this process" + } + } + example = { + "job_id": "748df7caa8c84a7ff6e", + "user_id": "bd6f9faf93b4", + "status": "running", + "process_graph": { + "process_id": "filter_daterange", + "args": [ + {"A": {"product_id": "Sentinel2A-L1C"}}, + {"from": "2017-01-01"}, + {"to": "2017-01-31"} + ] + }, + "submitted": "2017-01-01T09:32:12Z", + "last_update": "2017-01-01T09:36:18Z", + "consumed_credits": "392" + } diff --git a/src/graas_openeo_core_wrapper/endpoints.py b/src/openeo_grass_gis_driver/endpoints.py similarity index 59% rename from src/graas_openeo_core_wrapper/endpoints.py rename to src/openeo_grass_gis_driver/endpoints.py index ca27be49..67b174a8 100644 --- a/src/graas_openeo_core_wrapper/endpoints.py +++ b/src/openeo_grass_gis_driver/endpoints.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- -from openeo_core.app import flask_api -from graas_openeo_core_wrapper.capabilities import GRaaSCapabilities -from graas_openeo_core_wrapper.data import GRaaSData -from graas_openeo_core_wrapper.data_product_id import GRaaSDataProductId -from graas_openeo_core_wrapper.processes_process_id import GRaaSProcessesProcessId -from graas_openeo_core_wrapper.processes import GRaaSProcesses -from graas_openeo_core_wrapper.jobs import GRaaSJobs -from graas_openeo_core_wrapper.jobs_job_id import GRaaSJobsJobId -from graas_openeo_core_wrapper.udf import GRaaSUdf -from graas_openeo_core_wrapper.udf_lang_udf_type import GRaaSUdfType +from .app import flask_api +from .capabilities import GRaaSCapabilities +from .data import GRaaSData +from .data_product_id import GRaaSDataProductId +from .processes_process_id import GRaaSProcessesProcessId +from .processes import GRaaSProcesses +from .jobs import GRaaSJobs +from .jobs_job_id import GRaaSJobsJobId +from .udf import GRaaSUdf +from .udf_lang_udf_type import GRaaSUdfType __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" diff --git a/src/graas_openeo_core_wrapper/graph_db.py b/src/openeo_grass_gis_driver/graph_db.py similarity index 68% rename from src/graas_openeo_core_wrapper/graph_db.py rename to src/openeo_grass_gis_driver/graph_db.py index 825d8a36..ae1d1218 100644 --- a/src/graas_openeo_core_wrapper/graph_db.py +++ b/src/openeo_grass_gis_driver/graph_db.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from flask import json -from graas_openeo_core_wrapper.config import Config as GRaaSConfig -import requests +from .actinia_processing.config import Config as ActiniaConfig from sqlitedict import SqliteDict __license__ = "Apache License, Version 2.0" @@ -16,4 +14,4 @@ class GraphDB(SqliteDict): """ def __init__(self): - SqliteDict.__init__(self, filename=GRaaSConfig.GRAPH_DB, autocommit=True) + SqliteDict.__init__(self, filename=ActiniaConfig.GRAPH_DB, autocommit=True) diff --git a/src/graas_openeo_core_wrapper/jobs.py b/src/openeo_grass_gis_driver/jobs.py similarity index 56% rename from src/graas_openeo_core_wrapper/jobs.py rename to src/openeo_grass_gis_driver/jobs.py index c077db83..63cd92a7 100644 --- a/src/graas_openeo_core_wrapper/jobs.py +++ b/src/openeo_grass_gis_driver/jobs.py @@ -1,13 +1,11 @@ # -*- coding: utf-8 -*- -import pprint -from openeo_core.jobs import POST_JOBS_DOC -from openeo_core.jobs import Jobs -from graas_openeo_core_wrapper.graas_interface import GRaaSInterface from flask import make_response, jsonify, request from flask_restful_swagger_2 import swagger -from graas_openeo_core_wrapper.process_definitions import analyse_process_graph -import graas_openeo_core_wrapper -from graas_openeo_core_wrapper.graph_db import GraphDB +from flask_restful import Resource +from .definitions import ProcessGraph +from .actinia_processing import analyse_process_graph +from .graph_db import GraphDB +from .actinia_processing.actinia_interface import ActiniaInterface __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" @@ -15,11 +13,64 @@ __maintainer__ = "Soeren Gebbert" __email__ = "soerengebbert@googlemail.com" - -class GRaaSJobs(Jobs): +POST_JOBS_EXAMPLE = {"job_id": "42d5k3nd92mk49dmj294md"} + + +POST_JOBS_DOC = { + "summary": "submits a new job to the back-end", + "description": "creates a new job from one or more (chained) processes at the back-end, " + "which will eventually run the computations", + "tags": ["Job Management"], + "parameters": [ + { + "name": "evaluate", + "in": "query", + "description": "Defines how the job should be evaluated. Can be `lazy` (the default), `batch`, or " + "`sync` where lazy means that the job runs computations only on download requests " + "considering dynamically provided views. Batch jobs are immediately scheduled for " + "execution by the back-end. Synchronous jobs will be immediately executed and return " + "the result data.", + "type": "string", + "enum": ["lazy", "batch", "sync"], + "default": "lazy", + "required": False + }, + { + "name": "process_graph", + "in": "body", + "description": "Description of one or more (chained) processes including their input arguments", + "schema": ProcessGraph + }, + { + "name": "format", + "in": "query", + "description": "Description of the desired output format. Required in case `evaluate` is set to `sync`. " + "If not specified the format has to be specified in the download request.", + "type": "string", + "enum": ["nc", "json", "wcs", "wmts", "tms", "tif", "png", "jpeg"], + "required": False + } + ], + "responses": { + "200": { + "description": "Depending on the job evaluation type, the result of posting jobs can be either a json " + "description of the job (for lazy and batch jobs) or a result object such as a NetCDF " + "file (for sync jobs).", + "examples": { + "application/json": POST_JOBS_EXAMPLE + } + }, + "406": {"description": "The server is not capable to deliver the requested format."}, + "501": {"$ref": "#/responses/not_implemented"}, + "503": {"$ref": "#/responses/unavailable"} + } +} + + +class Jobs(Resource): def __init__(self): - self.iface = GRaaSInterface() + self.iface = ActiniaInterface() self.db = GraphDB() @swagger.doc(POST_JOBS_DOC) @@ -32,17 +83,17 @@ def put(self): try: # Empty the process location - graas_openeo_core_wrapper.PROCESS_LOCATION = {} + ActiniaInterface.PROCESS_LOCATION = {} process_graph = request.get_json() # Transform the process graph into a process chain and store the input location # Check all locations in the process graph result_name, process_list = analyse_process_graph(process_graph) - if len(graas_openeo_core_wrapper.PROCESS_LOCATION) == 0 or len(graas_openeo_core_wrapper.PROCESS_LOCATION) > 1: + if len(ActiniaInterface.PROCESS_LOCATION) == 0 or len(ActiniaInterface.PROCESS_LOCATION) > 1: return make_response(jsonify({"description":"Processes can only be defined for a single location!"}, 400)) - location = graas_openeo_core_wrapper.PROCESS_LOCATION.keys() + location = ActiniaInterface.PROCESS_LOCATION.keys() location = list(location)[0] status_code, mapsets = self.iface.list_mapsets(location=location) @@ -87,17 +138,17 @@ def post(self): try: # Empty the process location - graas_openeo_core_wrapper.PROCESS_LOCATION = {} + ActiniaInterface.PROCESS_LOCATION = {} process_graph = request.get_json() # Transform the process graph into a process chain and store the input location # Check all locations in the process graph result_name, process_list = analyse_process_graph(process_graph) - if len(graas_openeo_core_wrapper.PROCESS_LOCATION) == 0 or len(graas_openeo_core_wrapper.PROCESS_LOCATION) > 1: + if len(ActiniaInterface.PROCESS_LOCATION) == 0 or len(ActiniaInterface.PROCESS_LOCATION) > 1: return make_response(jsonify({"description":"Processes can only be defined for a single location!"}, 400)) - location = graas_openeo_core_wrapper.PROCESS_LOCATION.keys() + location = ActiniaInterface.PROCESS_LOCATION.keys() location = list(location)[0] process_chain = dict(list=process_list, diff --git a/src/graas_openeo_core_wrapper/jobs_job_id.py b/src/openeo_grass_gis_driver/jobs_job_id.py similarity index 53% rename from src/graas_openeo_core_wrapper/jobs_job_id.py rename to src/openeo_grass_gis_driver/jobs_job_id.py index 8faa9ec3..6052dde0 100644 --- a/src/graas_openeo_core_wrapper/jobs_job_id.py +++ b/src/openeo_grass_gis_driver/jobs_job_id.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- -import pprint -from openeo_core.jobs_job_id import GET_JOBS_ID_DOC, DELETE_JOBS_ID_DOC -from openeo_core.jobs_job_id import JobsJobId -from graas_openeo_core_wrapper.graas_interface import GRaaSInterface +from flask_restful import Resource +from .actinia_processing.actinia_interface import ActiniaInterface from flask import make_response, jsonify -from flask_restful_swagger_2 import swagger -from graas_openeo_core_wrapper.graph_db import GraphDB +from .graph_db import GraphDB +from .definitions import Job __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" @@ -13,14 +11,87 @@ __maintainer__ = "Soeren Gebbert" __email__ = "soerengebbert@googlemail.com" - -class GRaaSJobsJobId(JobsJobId): +GET_JOBS_ID_DOC = { + "summary": "Returns information about a submitted job", + "description": "Returns detailed information about a submitted job including its " + "current status and the underlying task", + "tags": ["Job Management"], + "parameters": [ + { + "name": "job_id", + "in": "path", + "type": "string", + "description": "job identifier string", + "required": True + } + ], + "responses": { + "200": {"description": "JSON object with job information.", "schema": Job}, + "401": {"$ref": "#/responses/auth_required"}, + "403": {"$ref": "#/responses/access_denied"}, + "404": {"description": "Job with specified identifier is not available"}, + "501": {"$ref": "#/responses/not_implemented"}, + "503": {"$ref": "#/responses/unavailable"} + } +} + +DELETE_JOBS_ID_EXAMPLE = { + "job_id": "42d5k3nd92mk49dmj294md", + "status": "scheduled", + "process_graph": { + "process_id": "slope", + "args": { + "dem": { + "process_id": "filter_bbox", + "args": { + "imagery": "/data/srtm90m", + "srs": "EPSG:4326", + "left": 6.301, + "right": 7.232, + "top": 53.87, + "bottom": 50.223 + } + } + } + }, + "submitted": "2017-01-01T09:32:12Z", + "user_id": "ab32e5f3a2bc2847s2" +} + +DELETE_JOBS_ID_DOC = { + "summary": "Deletes a submitted job", + "description": "Deleting a job will cancel execution at the back-end regardless of its status. " + "For finished jobs, this will also delete resulting data.", + "tags": ["Job Management"], + "parameters": [ + { + "name": "job_id", + "in": "path", + "type": "string", + "description": "job identifier string", + "required": True + } + ], + "responses": { + "200": { + "description": "JSON object with job information.", + "examples": {"application/json": DELETE_JOBS_ID_EXAMPLE} + }, + "401": {"$ref": "#/responses/auth_required"}, + "403": {"$ref": "#/responses/access_denied"}, + "404": {"description": "Job with specified identifier is not available"}, + "501": {"$ref": "#/responses/not_implemented"}, + "503": {"$ref": "#/responses/unavailable"} + } +} + + +class JobsJobId(Resource): def __init__(self): - self.iface = GRaaSInterface() + self.iface = ActiniaInterface() self.db = GraphDB() - @swagger.doc(GET_JOBS_ID_DOC) def get(self, job_id): try: @@ -52,7 +123,6 @@ def get(self, job_id): except Exception as e: return make_response(jsonify({"error": str(e)}), 500) - @swagger.doc(DELETE_JOBS_ID_DOC) def delete(self, job_id): try: diff --git a/src/graas_openeo_core_wrapper/main.py b/src/openeo_grass_gis_driver/main.py similarity index 76% rename from src/graas_openeo_core_wrapper/main.py rename to src/openeo_grass_gis_driver/main.py index a2add6c3..44b27616 100644 --- a/src/graas_openeo_core_wrapper/main.py +++ b/src/openeo_grass_gis_driver/main.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from openeo_core.app import flask_app -from graas_openeo_core_wrapper.endpoints import create_endpoints +from .app import flask_app +from .endpoints import create_endpoints __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" diff --git a/src/openeo_grass_gis_driver/processes.py b/src/openeo_grass_gis_driver/processes.py new file mode 100644 index 00000000..f3df8b3b --- /dev/null +++ b/src/openeo_grass_gis_driver/processes.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +from flask import make_response, jsonify +from flask_restful import Resource +from .actinia_processing import PROCESS_DESCRIPTION_DICT +from .definitions import ProcessListEntry + +__license__ = "Apache License, Version 2.0" +__author__ = "Sören Gebbert" +__copyright__ = "Copyright 20186, Sören Gebbert" +__maintainer__ = "Soeren Gebbert" +__email__ = "soerengebbert@googlemail.com" + + +GET_PROCESSES_EXAMPLE = [ + { + "process_id": "NDVI", + "description": "Computes the normalized difference vegetation index (NDVI) for " + "all pixels of the input dataset." + }, + { + "process_id": "median_time", + "description": "Applies median aggregation to pixel time series for all bands of the input dataset." + } +] + +GET_PROCESSES_DOC = { + "summary": "Returns processes supported by the back-end", + "description": "The request will ask the back-end for available processes and will return an array " + "of available processes with their unique identifiers and description", + "tags": ["Process Discovery"], + "parameters": [ + { + "name": "qname", + "in": "query", + "type": "string", + "description": "string expression to search for available processes by name", + "required": False + } + ], + "responses": { + "200": { + "description": "An array of EO processes including their unique identifiers and a description.", + "schema": { + "type": "array", + "items": ProcessListEntry + }, + "examples": GET_PROCESSES_EXAMPLE + }, + "401": {"$ref": "#/responses/auth_required"}, + "501": {"$ref": "#/responses/not_implemented"}, + "503": {"$ref": "#/responses/unavailable"} + } +} + + +class Processes(Resource): + + def get(self): + return make_response(jsonify(list(PROCESS_DESCRIPTION_DICT.keys())), 200) diff --git a/src/openeo_grass_gis_driver/processes_process_id.py b/src/openeo_grass_gis_driver/processes_process_id.py new file mode 100644 index 00000000..01fe32d9 --- /dev/null +++ b/src/openeo_grass_gis_driver/processes_process_id.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +from flask import make_response, jsonify +from .definitions import ProcessDescription +from flask_restful import abort, Resource +from .actinia_processing import PROCESS_DESCRIPTION_DICT + +__license__ = "Apache License, Version 2.0" +__author__ = "Sören Gebbert" +__copyright__ = "Copyright 20186, Sören Gebbert" +__maintainer__ = "Soeren Gebbert" +__email__ = "soerengebbert@googlemail.com" + +GET_PROCESSES_PROCESS_ID_EXAMPLE = { + "process_id": "median_time", + "description": "Applies median aggregation to pixel time series for all bands of the input dataset.", + "args": {"A": {"description": "input product (time series)"} + } +} + +GET_PROCESSES_PROCESS_ID_DOC = { + "summary": "Returns further information on a given EO process available at the back-end.", + "description": "The request will ask the back-end for further details about a process specified by identifier", + "tags": ["Process Discovery"], + "parameters": [ + { + "name": "process_id", + "in": "path", + "type": "string", + "description": "process identifier string such as `NDVI`", + "required": True + } + ], + "responses": { + "200": { + "description": "JSON object with metadata of the EO process.", + "schema": ProcessDescription, + "examples": {"application/json": GET_PROCESSES_PROCESS_ID_EXAMPLE} + }, + "401": {"$ref": "#/responses/auth_required"}, + "404": {"description": "Process with specified identifier is not available"}, + "501": {"$ref": "#/responses/not_implemented"}, + "503": {"$ref": "#/responses/unavailable"} + } +} + + +class ProcessesProcessId(Resource): + + def get(self, process_id): + + if process_id not in PROCESS_DESCRIPTION_DICT: + return make_response(jsonify({"description": "This process does not exists!"}), 400) + + return make_response(jsonify(PROCESS_DESCRIPTION_DICT[process_id]), 200) diff --git a/src/graas_openeo_core_wrapper/test_base.py b/src/openeo_grass_gis_driver/test_base.py similarity index 66% rename from src/graas_openeo_core_wrapper/test_base.py rename to src/openeo_grass_gis_driver/test_base.py index 904346db..ad42c376 100644 --- a/src/graas_openeo_core_wrapper/test_base.py +++ b/src/openeo_grass_gis_driver/test_base.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- import unittest -from openeo_core.app import flask_api -from graas_openeo_core_wrapper.endpoints import create_endpoints -from graas_openeo_core_wrapper.config import Config as GRaaSConfig +from .app import flask_api +from .endpoints import create_endpoints +from .actinia_processing.config import Config as ActiniaConfig __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert" @@ -17,5 +17,5 @@ class TestBase(unittest.TestCase): def setUp(self): self.app = flask_api.app.test_client() - self.gconf = GRaaSConfig() + self.gconf = ActiniaConfig() self.gconf.PORT = "8080" diff --git a/src/openeo_grass_gis_driver/udf.py b/src/openeo_grass_gis_driver/udf.py new file mode 100644 index 00000000..0faa408c --- /dev/null +++ b/src/openeo_grass_gis_driver/udf.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +from flask import make_response, jsonify +from flask_restful import Resource +from .definitions import UDFTypen + +__license__ = "Apache License, Version 2.0" +__author__ = "Sören Gebbert" +__copyright__ = "Copyright 20186, Sören Gebbert" +__maintainer__ = "Soeren Gebbert" +__email__ = "soerengebbert@googlemail.com" + + +GET_UDF_EXAMPLE = { + "R": { + "udf_types": [ + "reduce_time", + "reduce_space", + "apply_pixel" + ], + "versions": { + "3.1.0": { + "packages": [ + "Rcpp_0.12.10", + "sp_1.2-5", + "rmarkdown_1.6" + ] + }, + "3.3.3": { + "packages": [ + "Rcpp_0.12.10", + "sf_0.5-4", + "spacetime_1.2-0" + ] + } + } + } +} + + +SUPPORTED_UDF = { + "python": { + "udf_types": [ + "reduce_time" + ], + "versions": { + "3.6": { + "packages": [ + "numpy", + "scipy" + ] + } + } + } +} + +GET_UDF_DOC = { + "summary": "Describes how custom user-defined functions can be exposed to the data and " + "which languages are supported by the back-end.", + "tags": ["UDF"], + "responses": { + "200": { + "description": "Description of UDF support", + "schema": { + "type": "array", + "items": { + "type": "object", + "description": "A map with language identifiers such as `R` as keys and an object that " + "defines available versions, extension packages, and UDF schemas.", + "additionalProperties": { + "type": "object", + "properties": { + "udf_types": { + "type": "array", + "items": UDFTypen + }, + "versions": { + "type": "object", + "description": "A map with version identifiers as keys and an object value that " + "specifies which extension packages are available for the " + "particular version.", + "additionalProperties": { + "description": "Extension package identifiers that should include their version " + "number such as `'sf__0.5-4'`", + "properties": { + "packages": {"type": "array", "items": {"type": "string"}} + } + } + } + } + } + } + }, + "examples": {"application/json": GET_UDF_EXAMPLE} + }, + "401": {"$ref": "#/responses/auth_required"}, + "403": {"$ref": "#/responses/access_denied"}, + "501": {"$ref": "#/responses/not_implemented"}, + "503": {"$ref": "#/responses/unavailable"} + } +} + + +class Udf(Resource): + def get(self): + return make_response(jsonify(SUPPORTED_UDF), 200) diff --git a/src/openeo_grass_gis_driver/udf_lang_udf_type.py b/src/openeo_grass_gis_driver/udf_lang_udf_type.py new file mode 100644 index 00000000..fa5405f0 --- /dev/null +++ b/src/openeo_grass_gis_driver/udf_lang_udf_type.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +from .actinia_processing.actinia_interface import ActiniaInterface +from flask import make_response, jsonify +from .graph_db import GraphDB +from .actinia_processing import udf_reduce_time +from flask_restful import Resource +from .definitions import UDFDescription + +__license__ = "Apache License, Version 2.0" +__author__ = "Sören Gebbert" +__copyright__ = "Copyright 20186, Sören Gebbert" +__maintainer__ = "Soeren Gebbert" +__email__ = "soerengebbert@googlemail.com" + + +python_udfs = dict(python={}) +python_udfs["python"][udf_reduce_time.PROCESS_NAME] = udf_reduce_time.DOC + +GET_UDF_TYPE_EXAMPLE = UDFDescription.example + +GET_UDF_TYPE_DOC = { + "summary": "Returns the process description of UDF schemas, which offer different possibilities how " + "user-defined scripts can be applied to the data.", + "tags": ["UDF"], + "parameters": [ + { + "name": "lang", + "in": "path", + "description": "Language identifier such as `R`", + "type": "string", + "enum": ["python", "R"], + "required": True + }, + { + "name": "udf_type", + "in": "path", + "type": "string", + "description": "The UDF types define how UDFs can be exposed to the data, how they can be parallelized, " + "and how the result schema should be structured.", + "enum": ["apply_pixel", "apply_scene", "reduce_time", "reduce_space", "window_time", "window_space", + "window_spacetime", "aggregate_time", "aggregate_space", "aggregate_spacetime"], + "required": True + } + ], + "responses": { + "200": { + "description": "Process description", + "schema": UDFDescription, + "examples": {"application/json": GET_UDF_TYPE_EXAMPLE} + }, + "401": {"$ref": "#/responses/auth_required"}, + "403": {"$ref": "#/responses/access_denied"}, + "404": {"description": "UDF type with specified identifier is not available"}, + "501": {"description": "This API feature, language or UDF type is not supported by the back-end."}, + "503": {"$ref": "#/responses/unavailable"} + } +} + + +class UdfType(Resource): + + def __init__(self): + self.iface = ActiniaInterface() + self.db = GraphDB() + + def get(self, lang, udf_type): + + if lang not in python_udfs: + return make_response(jsonify({"description": "UDF type with " + "specified identifier is not available"}), 404) + + if udf_type not in python_udfs[lang]: + return make_response(jsonify({"description": "UDF type with " + "specified identifier is not available"}), 404) + + return make_response(jsonify(python_udfs[lang][udf_type]), 200) diff --git a/tests/conftest.py b/tests/conftest.py index fd9f108f..049ef5ab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ - Dummy conftest.py for graas_openeo_core_wrapper. + Dummy conftest.py for openeo_grass_gis_driver. If you don't know what this is for, just leave it empty. Read more about conftest.py under: diff --git a/tests/test_capabilities.py b/tests/test_capabilities.py index f556a8ed..6c5f055d 100644 --- a/tests/test_capabilities.py +++ b/tests/test_capabilities.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from flask import json import unittest -from graas_openeo_core_wrapper.capabilities import GRAAS_CAPABILITIES -from graas_openeo_core_wrapper.test_base import TestBase +from openeo_grass_gis_driver.capabilities import GRAAS_CAPABILITIES +from openeo_grass_gis_driver.test_base import TestBase __license__ = "Apache License, Version 2.0" __author__ = "Sören Gebbert"