diff --git a/.gitignore b/.gitignore index e30a284..13f6faf 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ pywps_demo.egg-info/ # docs build artifacts _build/ + +temp/ \ No newline at end of file diff --git a/LICENCE.txt b/LICENCE.txt index b4ccd9f..6513adc 100644 --- a/LICENCE.txt +++ b/LICENCE.txt @@ -1,4 +1,7 @@ +MIT License + Copyright (C) 2015 PyWPS Development Team, represented by Jachym Cepicky +Copyright (C) 2020 Idan Miara, Ben Avrahami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to diff --git a/README_TALOS.rst b/README_TALOS.rst new file mode 100644 index 0000000..8246b8e --- /dev/null +++ b/README_TALOS.rst @@ -0,0 +1,15 @@ +:Name: talos-wps +:Authors: Idan Miara, Ben Avrahami + +.. |license| image:: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square + :target: https://github.com/talos-gis/gdalos/raw/master/LICENSE + +.. |GDAL| image:: https://github.com/OSGeo/gdal/raw/master/gdal/data/gdalicon.png?style=flat-square + :target: https://github.com/OSGeo/gdal + +|license| + +pywps service for some of our neat gdal processes + +* gdaldem - crops to an extent and/or to a cutline polygon[s] and/or makes a color relief +* viewshed - runs gdal.ViewshedGenerate diff --git a/VERSION.txt b/VERSION.txt index fae6e3d..b1b25a5 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -4.2.1 +2.2.2 diff --git a/app.py b/app.py new file mode 100644 index 0000000..bedf986 --- /dev/null +++ b/app.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2016 PyWPS Project Steering Committee +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import flask + +# we need to set the root before we import the main_page as there are relative paths to this root (i.e. config files) +import sys +import os +project_root = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, project_root) +os.chdir(project_root) + +from app_main_page import main_page + +app = flask.Flask(__name__) +app.register_blueprint(main_page) + +application = app # application is the default name for mod_wsgi + +if __name__ == "__main__": + app.run() diff --git a/app_config.py b/app_config.py new file mode 100644 index 0000000..fe0653a --- /dev/null +++ b/app_config.py @@ -0,0 +1,17 @@ +import pywps.configuration as config +from app_set_server import set_server +from set_root import set_root + +set_root() + +config_path = 'config/' +cfgfiles = [config_path + 'pywps.cfg'] +config.load_configuration(cfgfiles) + +# these defaults will be overwritten with the url from by the server\url from the config file +server_hostname = 'http://localhost' +server_port = 5000 +server_base_url = '{}:{}'.format(server_hostname, server_port) +server_wps_url = server_base_url + '/wps' + +server_hostname, server_port, server_base_url, server_wps_url, _ = set_server(config.get_config_value("server", "url")) diff --git a/app_debug.py b/app_debug.py new file mode 100644 index 0000000..8f1ea1d --- /dev/null +++ b/app_debug.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2016 PyWPS Project Steering Committee +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import flask + +# we need to set the root before we import the main_page as there are relative paths to this root (i.e. config files) +import sys +import os + + +my_modules = [ + r'D:\dev\gis\gdal\gdal\swig\python\gdal-utils', + r'D:\dev\gis\pywps', + r'D:\dev\gis\gdalos\src', + r'D:\dev\gis\TaLoS\1\p\talos\src', +] +for m in my_modules: + print(f'adding {m} to path') + sys.path.insert(0, m) + +project_root = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, project_root) +os.chdir(project_root) + +if __name__ == "__main__": + from demo import main + main() diff --git a/app_main_page.py b/app_main_page.py new file mode 100644 index 0000000..b7f7a2c --- /dev/null +++ b/app_main_page.py @@ -0,0 +1,67 @@ +import os +import sys +import flask +import pywps +import processes +import app_config + +# This is, how you start PyWPS instance +service = pywps.Service(processes=processes.processes) +# config is read in app_config so we don't need to pass it to Service as well +# service = pywps.Service(processes=processes.processes, cfgfiles=cfgfiles) + +main_page = flask.Blueprint('main_page', __name__, template_folder='templates') + + +@main_page.route('/test') +def test(): + return 'hello test!' + + +@main_page.route("/sys_path") +def sys_path(): + return str(sys.path) + + +# returns 'hello to the WPS server root' +@main_page.route('/wps', methods=['GET', 'POST']) +def wps(): + return service + + +@main_page.route("/") +def hello(): + request_url = flask.request.url + return flask.render_template('home.html', request_url=request_url, + server_url=app_config.server_wps_url, + process_descriptor=processes.process_descriptor) + + +def flask_response(targetfile): + if os.path.isfile(targetfile): + with open(targetfile, mode='rb') as f: + file_bytes = f.read() + file_ext = os.path.splitext(targetfile)[1] + mime_type = 'text/xml' if 'xml' in file_ext else None + return flask.Response(file_bytes, content_type=mime_type) + else: + flask.abort(404) + + +@main_page.route('/outputs/' + '') +def outputfile(filename): + targetfile = os.path.join('outputs', filename) + return flask_response(targetfile) + + +@main_page.route('/data/' + '') +def datafile(filename): + targetfile = os.path.join('static', 'data', filename) + return flask_response(targetfile) + + +# not sure how the static route works. static route doesn't reach this function. +@main_page.route('/static/' + '') +def staticfile(filename): + targetfile = os.path.join('static', filename) + return flask_response(targetfile) diff --git a/app_set_server.py b/app_set_server.py new file mode 100644 index 0000000..fc1be29 --- /dev/null +++ b/app_set_server.py @@ -0,0 +1,39 @@ +import re +from exceptions import BadUserInputError + + +def set_server(new_server_wps_url: str): + pattern = r'((?:.*://)?(.*?)(?:(?::)(\d+))?(?:/.*?)?)$' + m = re.match(pattern, new_server_wps_url) + if not m: + raise BadUserInputError('cannot parse server url: {}'.format(new_server_wps_url)) + # print(m.groups()) + server_wps_url = m.group(1) + if server_wps_url.endswith('/'): + server_wps_url = server_wps_url.rstrip('/') + server_base_url = server_wps_url + if server_base_url.endswith('/wps'): + server_base_url = server_wps_url[:-4] + else: + server_wps_url = server_base_url + '/wps' + server_hostname = m.group(2) + server_port = m.group(3) + if server_port: + server_port = int(server_port) + else: + server_port = 80 + return server_hostname, server_port, server_base_url, server_wps_url, new_server_wps_url + + +if __name__ == '__main__': + print(set_server('http://localhost:5000/abc/wps')) + print(set_server('http://localhost:5000/wps')) + print(set_server('http://localhost:5000/')) + print(set_server('http://localhost:5000')) + print(set_server('http://localhost/abc/wps')) + print(set_server('http://localhost/wps')) + print(set_server('http://localhost/')) + print(set_server('localhost')) + print(set_server('localhost:5000')) + print(set_server('localhost:5000/')) + print(set_server('localhost:5000/wps')) \ No newline at end of file diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/formats.py b/backend/formats.py new file mode 100644 index 0000000..b46a19d --- /dev/null +++ b/backend/formats.py @@ -0,0 +1,4 @@ +from pywps import FORMATS, Format + +czml_format = Format('application/czml+json', extension='.czml') +wkt_format = Format('application/wkt', extension='.wkt') diff --git a/config/instances/testing.yaml b/config/instances/testing.yaml new file mode 100644 index 0000000..7b64fe5 --- /dev/null +++ b/config/instances/testing.yaml @@ -0,0 +1,25 @@ +url: 'http://localhost:5000/wps' +allowedinputpaths: + - './static/' + - 'd:\Maps' + - 'd:\MRR' +default_mimetype: 'text/xml' +#default_mimetype: 'application/json' +#json_indent: -1 +json_indent: 2 + +#root: './static/data/maps' +root: 'd:\Maps' + +dtm_color_palette: './static/data/color_files/color_file_1.txt' + +dtm_geo: './static/data/maps/srtm1_x35_y32.tif' +#dtm_geo: '@xlink:href=file:./static/data/maps/srtm1_x35_y32.tif' + +dtm_utm: 'd:\maps\srtm3\utm\*.tif' +#dtm_utm: './static/data/maps/srtm1_w84u36.tif' + +radio_refractivity: 333.0 +radio_conductivity: 3.0 +radio_permittivity: 33.0 +radio_humidity: 33.0 diff --git a/config/templates/process_defaults.yaml b/config/templates/process_defaults.yaml new file mode 100644 index 0000000..af9ae61 --- /dev/null +++ b/config/templates/process_defaults.yaml @@ -0,0 +1,48 @@ +say_hello: + name: "default-config" + +ls: + dir: '{{ root }}' + pattern: '*.tif' + +ras_val: + r: '{{ dtm_geo }}' + +crop_color: + r: '{{ dtm_geo }}' + of: 'czml' +# color_palette: '{{ dtm_color_palette }}' + +viewshed: + r: '{{ dtm_utm }}' + of: 'czml' + in_crs: '0' + out_crs: '0' + backend: 'talos' + + refractivity: {{ radio_refractivity }} + conductivity: {{ radio_conductivity }} + permittivity: {{ radio_permittivity }} + humidity: {{ radio_humidity }} + + fill_center: False + profile_extension: True + +tester: + r: '{{ dtm_geo }}' + name: 'Tester' + +los: + of: 'json' + r: '{{ dtm_utm }}' + in_crs: '0' + out_crs: '0' + backend: 'radio' + + refractivity: {{ radio_refractivity }} + conductivity: {{ radio_conductivity }} + permittivity: {{ radio_permittivity }} + humidity: {{ radio_humidity }} + + fill_center: False + profile_extension: True diff --git a/pywps.cfg b/config/templates/pywps.cfg similarity index 65% rename from pywps.cfg rename to config/templates/pywps.cfg index 01be2c2..e8d3aac 100644 --- a/pywps.cfg +++ b/config/templates/pywps.cfg @@ -1,13 +1,13 @@ [metadata:main] -identification_title=PyWPS Demo server -identification_abstract=PyWPS testing and development server. Do NOT use this server in production environment. You shall setup PyWPS as WSGI application for production. Please refer documentation for further detials. -identification_keywords=WPS,GRASS,PyWPS, Demo, Dev +identification_title=TaLoS WPS server +identification_abstract=TaLoS WPS server server. +identification_keywords=WPS,GRASS,PyWPS,GDAL,Dev identification_keywords_type=theme identification_fees=None identification_accessconstraints=None -provider_name=PyWPS Developement team -provider_url=http://pywps.org/' -contact_name=Your Name +provider_name=TaLoS GIS Development team +provider_url=https://github.com/talos-gis/pywps-flask +contact_name=Idan Miara contact_position=Developer contact_address=My Street contact_city=My City @@ -17,7 +17,7 @@ contact_country=World, Internet contact_phone=+00 00 11 22 33 contact_fax=+00 99 88 77 66 contact_email=info@yourdomain.org -contact_url=http://pywps.org +contact_url=https://github.com/talos-gis contact_hours=8:00-20:00UTC contact_instructions=Knock on the door contact_role=pointOfContact @@ -25,14 +25,17 @@ contact_role=pointOfContact [server] maxsingleinputsize=1mb maxrequestsize=3mb -url=http://localhost:5000/wps -outputurl=http://localhost:5000/outputs/ +url={{ url }} +outputurl=outputs outputpath=outputs workdir=workdir wd_inp_subdir=inputs wd_out_subdir=outputs maxprocesses=10 -parallelprocesses=2 +parallelprocesses=-1 +allowedinputpaths={% for path in allowedinputpaths %}{{path}};{% endfor %} +default_mimetype={{ default_mimetype }} +json_indent={{ json_indent }} [processing] mode=docker diff --git a/demo.py b/demo.py index e3eff59..6e786dd 100755 --- a/demo.py +++ b/demo.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 # Copyright (c) 2016 PyWPS Project Steering Committee -# -# +# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,90 +22,11 @@ # SOFTWARE. import os -import flask -import pywps -from pywps import Service +from app import app +from app_config import server_port -from processes.sleep import Sleep -from processes.ultimate_question import UltimateQuestion -from processes.centroids import Centroids -from processes.sayhello import SayHello -from processes.feature_count import FeatureCount -from processes.buffer import Buffer -from processes.area import Area -from processes.bboxinout import Box -from processes.jsonprocess import TestJson - - -app = flask.Flask(__name__) - -processes = [ - FeatureCount(), - SayHello(), - Centroids(), - UltimateQuestion(), - Sleep(), - Buffer(), - Area(), - Box(), - TestJson() -] - -# For the process list on the home page - -process_descriptor = {} -for process in processes: - abstract = process.abstract - identifier = process.identifier - process_descriptor[identifier] = abstract - -# This is, how you start PyWPS instance -service = Service(processes, ['pywps.cfg']) - - -@app.route("/") -def hello(): - server_url = pywps.configuration.get_config_value("server", "url") - request_url = flask.request.url - return flask.render_template('home.html', request_url=request_url, - server_url=server_url, - process_descriptor=process_descriptor) - - -@app.route('/wps', methods=['GET', 'POST']) -def wps(): - - return service - - -@app.route('/outputs/'+'') -def outputfile(filename): - targetfile = os.path.join('outputs', filename) - if os.path.isfile(targetfile): - file_ext = os.path.splitext(targetfile)[1] - with open(targetfile, mode='rb') as f: - file_bytes = f.read() - mime_type = None - if 'xml' in file_ext: - mime_type = 'text/xml' - return flask.Response(file_bytes, content_type=mime_type) - else: - flask.abort(404) - - -@app.route('/static/'+'') -def staticfile(filename): - targetfile = os.path.join('static', filename) - if os.path.isfile(targetfile): - with open(targetfile, mode='rb') as f: - file_bytes = f.read() - mime_type = None - return flask.Response(file_bytes, content_type=mime_type) - else: - flask.abort(404) - -if __name__ == "__main__": +def main(): import argparse parser = argparse.ArgumentParser( @@ -115,30 +36,33 @@ def staticfile(filename): It's intended to be running in test environment only! For more documentation, visit http://pywps.org/doc """ - ) + ) parser.add_argument('-d', '--daemon', action='store_true', help="run in daemon mode") - parser.add_argument('-a','--all-addresses', - action='store_true', help="run flask using IPv4 0.0.0.0 (all network interfaces)," + - "otherwise bind to 127.0.0.1 (localhost). This maybe necessary in systems that only run Flask") + parser.add_argument('-a', '--all-addresses', + action='store_true', help="run flask using IPv4 0.0.0.0 (all network interfaces)," + + "otherwise bind to 127.0.0.1 (localhost). This maybe necessary in systems that only run Flask") args = parser.parse_args() - + if args.all_addresses: - bind_host='0.0.0.0' + bind_host = '0.0.0.0' else: - bind_host='127.0.0.1' + bind_host = '127.0.0.1' if args.daemon: pid = None try: pid = os.fork() except OSError as e: - raise Exception("%s [%d]" % (e.strerror, e.errno)) + print("%s [%d]" % (e.strerror, e.errno)) if (pid == 0): os.setsid() - app.run(threaded=True,host=bind_host) + app.run(threaded=True, host=bind_host, port=server_port) else: os._exit(0) else: - app.run(threaded=True,host=bind_host) + app.run(threaded=True, host=bind_host, port=server_port) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/docker/gdal/gunicorn/Dockerfile b/docker/gdal/gunicorn/Dockerfile new file mode 100644 index 0000000..9986bf1 --- /dev/null +++ b/docker/gdal/gunicorn/Dockerfile @@ -0,0 +1,26 @@ +#FROM osgeo/gdal:latest +FROM osgeo/gdal:ubuntu-small-latest + +# ENV WORKDIR=/usr/src/app +ENV WORKDIR=/app + +RUN apt-get install -y --no-install-recommends python3-distutils python3-pip python3-setuptools python3-wheel +COPY requirements.txt ./ +RUN python3 -m pip install -r requirements.txt + +# https://github.com/moby/moby/issues/1996#issuecomment-185872769 +ARG CACHEBUST=1 + +RUN python3 -m pip install --upgrade gdalos +# RUN python3 -m pip install --index-url https://test.pypi.org/simple/ --upgrade gdalos + +WORKDIR ${WORKDIR} +COPY ./src ./src +COPY ./data/sample ./data/sample +COPY ./data/config ./static/config + +COPY src/patch/core.py /usr/local/lib/python3.8/dist-packages/czml3/ + +RUN mkdir -p ./logs ./outputs ./workdir ./static/data/maps + +CMD gunicorn --bind 0.0.0.0:5000 --chdir ${WORKDIR}/src app:app \ No newline at end of file diff --git a/docker/gdal/gunicorn/docker_test.sh b/docker/gdal/gunicorn/docker_test.sh new file mode 100644 index 0000000..82dcc8c --- /dev/null +++ b/docker/gdal/gunicorn/docker_test.sh @@ -0,0 +1,41 @@ +shopt -s expand_aliases + +sudo docker container stop $(sudo docker container ls -aq) && sudo docker container rm $(sudo docker container ls -aq) + +sudo docker build -t talos_wps:latest -t talos_wps:latest . + +sudo docker run \ + --rm \ + -it \ + -p 5000:5000 \ + --name talos_wps_test \ + --env WORKDIR=/app \ + --mount type=bind,source=/home/idan/maps,target=/app/static/data/maps,readonly \ + --mount type=bind,source=/home/idan/dev/gis/talos_wps/data/config,target=/app/data/config,readonly \ + talos_wps:latest + +# release image +#talosgis/talos_wps:release-1.1.3 + +server=http://localhost:5000/wps + +curl "$server?service=WPS&request=execute&version=1.0.0&Identifier=say_hello&storeExecuteResponse=true&DataInputs=name=Idan&RawDataOutput=output" + + +sudo docker start talos_wps_test +sudo docker restart talos_wps_test + +sudo docker exec -it talos_wps_test bash + +sudo docker ps -all + +sudo docker system prune + +To stop all running containers use the docker container stop command followed by a list of all containers IDs. +$ sudo docker container stop $(sudo docker container ls -aq) + +Once all containers are stopped, you can remove them using the docker container rm command followed by the containers ID list. +$ sudo docker container rm $(sudo docker container ls -aq) + +# compare images +sudo container-diff diff daemon://talos_wps:latest daemon://talosgis/talos_wps:release-1.1.3 --type=file diff --git a/docs/conf.py b/docs/conf.py index d781f9f..082de71 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -243,11 +243,14 @@ #'figure_align': 'htbp', } +project_name = 'PyWPS-Flask' +project_doc = project_name+' Documentation' + # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'PyWPS-Flask.tex', u'PyWPS-Flask Documentation', + (master_doc, 'PyWPS-Flask.tex', project_doc, u'PyWPS Development Team', 'manual'), ] @@ -277,7 +280,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'pywps-flask', u'PyWPS-Flask Documentation', + (master_doc, project_name, project_doc, [author], 1) ] @@ -291,8 +294,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'PyWPS-Flask', u'PyWPS-Flask Documentation', - author, 'PyWPS-Flask', 'One line description of project.', + (master_doc, project_name, project_doc, + author, project_name, 'One line description of project.', 'Miscellaneous'), ] diff --git a/exceptions.py b/exceptions.py new file mode 100644 index 0000000..d82cb09 --- /dev/null +++ b/exceptions.py @@ -0,0 +1,11 @@ +class PyWPSError(Exception): + """Exception class from which every exception in this library will derive. + It enables other projects using this library to catch all errors coming + from the library with a single "except" statement + """ + pass + + +class BadUserInputError(PyWPSError): + """A specific error""" + pass diff --git a/generate_configs.py b/generate_configs.py new file mode 100644 index 0000000..a5e49d3 --- /dev/null +++ b/generate_configs.py @@ -0,0 +1,59 @@ +import sys + +import jinja2 +import os +from pathlib import Path +import yaml + + +def generate_configs(parameter_file, output_directory, template_root): + output_directory = Path(output_directory) + template_root = Path(template_root) + parameter_file = Path(parameter_file) + template_files = list(p.relative_to(template_root) for p in template_root.rglob('*.*')) + + if os.path.exists(parameter_file): + try: + stream = open(parameter_file, 'r') + parameter_dict = yaml.safe_load(stream) + except Exception as err: + raise Exception(f'Cannot process parameter file {parameter_file}') + else: + raise Exception(f'Parameter file {parameter_file} not found!') + + if not os.path.exists(output_directory): + os.mkdir(output_directory) + + env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath=template_root)) + outputs = [] + for template_file in template_files: + template = env.get_template(str(template_file)) + result = template.render(parameter_dict) + output_file = output_directory / template_file + outputs.append(output_file) + f = open(output_file, "w") + f.write(result) + f.close() + print(f'configs created: {outputs}') + + +if __name__ == '__main__': + root = Path(os.path.abspath(__file__)).parent + if len(sys.argv) > 1: + _parameter_file = sys.argv[1] + else: + _parameter_file = root / 'config/instances/testing.yaml' + + if len(sys.argv) > 2: + _output_directory = sys.argv[2] + else: + _output_directory = root / 'config' + + if len(sys.argv) > 3: + _template_root = sys.argv[3] + else: + _template_root = root / 'config/templates' + + generate_configs(parameter_file=_parameter_file, + output_directory=_output_directory, + template_root=_template_root) diff --git a/installer/install-1-python.bat b/installer/install-1-python.bat new file mode 100644 index 0000000..00d657f --- /dev/null +++ b/installer/install-1-python.bat @@ -0,0 +1,31 @@ +::AT > NUL +@NET SESSION >nul 2>&1 +@IF %ERRORLEVEL% EQU 0 ( + goto install +) ELSE ( + @ECHO you are NOT Administrator. Please run this script as Administrator. Exiting... + goto finish +) + +:install + +set online=%1 + +pushd "%~dp0" + +@echo installation paths +set PYTHONHOME=C:\Python38 +set python_inst=python-3.8.6-amd64.exe + +@echo step 1: installing python +if %1x==x ( + @echo to skip python installation run this installer with any argument for installing python as well, i.e.: + @echo %~nx0 x + %python_inst% /passive InstallAllUsers=1 PrependPath=1 TargetDir=%PYTHONHOME% +) else ( + @echo skipping python installtion +) + + +:finish +pause \ No newline at end of file diff --git a/installer/install-2-talos_wps.bat b/installer/install-2-talos_wps.bat new file mode 100644 index 0000000..f3d7546 --- /dev/null +++ b/installer/install-2-talos_wps.bat @@ -0,0 +1,53 @@ +::AT > NUL +@NET SESSION >nul 2>&1 +@IF %ERRORLEVEL% EQU 0 ( + goto install +) ELSE ( + @ECHO you are NOT Administrator. Please run this script as Administrator. Exiting... + goto finish +) + +:install + +pushd "%~dp0" + +@echo installation paths +set PYTHONHOME=C:\Python38\ +if not exist "%PYTHONHOME%" set PYTHONHOME= + +set talos_wps=c:\talos_wps +set wheels=%~dp0\wheels\ + +@echo talos_wps install files +set talos_wps_7z=%~dp0\talos_wps_install\talos_wps.7z + +@echo step 2: git clone or extract talos_wps +if exist %talos_wps_7z% ( + 7za x %talos_wps_7z% -aoa -o%talos_wps% +) else ( + git clone https://github.com/talos-gis/pywps-flask.git %talos_wps% + pushd "%~dp0" + cd /d %talos_wps% + ::git checkout talos_wps + git pull + popd +) + +set online= +if "%1x" neq "x" set online=y +if not exist %wheels% set online=y + +set pip_offline= +if %online%x==x set pip_offline=--upgrade --no-index --find-links %wheels% + +@echo step 3: install talos_wps python package requirements +::%PYTHONHOME%python -m pip install %pip_offline% -r %talos_wps%\requirements-iis.txt +%PYTHONHOME%python -m pip install --force-reinstall %pip_offline% -r %talos_wps%\requirements.txt +%PYTHONHOME%python -m pip install --force-reinstall %pip_offline% -r %talos_wps%\requirements-opt.txt + +popd + +@echo done! + +:finish +pause \ No newline at end of file diff --git a/installer/make_all.bat b/installer/make_all.bat new file mode 100644 index 0000000..04355bb --- /dev/null +++ b/installer/make_all.bat @@ -0,0 +1,10 @@ +echo: make an installer? +pause + +call make_talos_wps.bat a +call make_talos_wheels.bat a +call make_pip_wheels.bat a + +@echo done! + +pause \ No newline at end of file diff --git a/installer/make_install.bat b/installer/make_install.bat new file mode 100644 index 0000000..5145f88 --- /dev/null +++ b/installer/make_install.bat @@ -0,0 +1,46 @@ +set online=%1 + +pushd "%~dp0" + +@echo installation paths +set PYTHONHOME=C:\Python38\ +if not exist "%PYTHONHOME%" set PYTHONHOME= + +set talos_wps=%~dp0\talos_wps +set wheels=%~dp0\wheels\ +set talos_gis=D:\dev\gis\TaLoS\1\p\talos + +@echo talos_wps install files +set talos_wps_7z=%~dp0\talos_wps_install\talos_wps.7z +::set talos_7z=%~dp0\talos_wps_install\talos.7z + +@echo step 2: git clone or extract talos_wps +git clone https://github.com/talos-gis/pywps-flask.git %talos_wps% +pushd "%~dp0" +cd /d %talos_wps% +git checkout talos_wps +git pull +popd + +7za a %talos_wps_7z% %talos_wps%\* +::7za a %talos_7z% %PYTHONHOME%talos.dll + +@echo step 3: install talos_wps python package requirements +%PYTHONHOME%python -m pip download -r %talos_wps%\requirements.txt -d %wheels% +::%PYTHONHOME%python -m pip download -r %talos_wps%\requirements-opt.txt -d %wheels% +%PYTHONHOME%python -m pip download -r %talos_wps%\requirements-iis.txt -d %wheels% +%PYTHONHOME%python -m pip download -r %talos_wps%\requirements-apache.txt -d %wheels% + +pushd %talos_gis% +rmdir /s/q dist +rmdir /s/q build +%PYTHONHOME%python setup.py bdist_wheel +copy dist\*.whl %wheels% +popd + +popd + +@echo done! + +:finish +pause \ No newline at end of file diff --git a/installer/make_pip_wheels.bat b/installer/make_pip_wheels.bat new file mode 100644 index 0000000..f65899d --- /dev/null +++ b/installer/make_pip_wheels.bat @@ -0,0 +1,26 @@ +if %1x==x ( + echo: make an installer? + pause +) + + +pushd "%~dp0" + +@echo installation paths +set PYTHONHOME=C:\Python38 +set talos_wps=%~dp0\talos_wps +set wheels=%~dp0\wheels\ + +mkdir %wheels% + +@echo step 3: install talos_wps python package requirements +%PYTHONHOME%\python -m pip download -r %talos_wps%\requirements.txt -d %wheels% +::%PYTHONHOME%\python -m pip download -r %talos_wps%\requirements-opt.txt -d %wheels% +%PYTHONHOME%\python -m pip download -r %talos_wps%\requirements-iis.txt -d %wheels% +%PYTHONHOME%\python -m pip download -r %talos_wps%\requirements-apache.txt -d %wheels% + +popd + +@echo done! + +if %1x==x pause \ No newline at end of file diff --git a/installer/make_talos_wheels.bat b/installer/make_talos_wheels.bat new file mode 100644 index 0000000..ef79c76 --- /dev/null +++ b/installer/make_talos_wheels.bat @@ -0,0 +1,28 @@ +if %1x==x ( + echo: make an installer? + pause +) + +set talos_gis=D:\dev\gis\TaLoS\1\p\talos +set gdalos=d:\dev\gis\gdalos +set wheels="%~dp0\wheels\" + +set talos_wps_install_dir=%~dp0\talos_wps_install +set PYTHONHOME=C:\Python38 +set talos_7z=%talos_wps_install_dir%\talos.7z + +mkdir %wheels% +for %%m in (%talos_gis%, %gdalos%) do ( + pushd %%m + rmdir /s/q dist + rmdir /s/q build + python setup.py sdist bdist_wheel + copy dist\*.whl %wheels% + popd +) + + +mkdir %talos_wps_install_dir% +7za a %talos_7z% %PYTHONHOME%\talos.dll + +if %1x==x pause \ No newline at end of file diff --git a/installer/make_talos_wps.bat b/installer/make_talos_wps.bat new file mode 100644 index 0000000..0280849 --- /dev/null +++ b/installer/make_talos_wps.bat @@ -0,0 +1,30 @@ +if %1x==x ( + echo: make an installer? + pause +) + +pushd "%~dp0" + +@echo installation paths +set talos_wps=%~dp0\talos_wps +set talos_wps_install_dir=%~dp0\talos_wps_install +set talos_wps_7z=%talos_wps_install_dir%\talos_wps.7z + +@echo talos_wps install files + +@echo step 2: git clone or extract talos_wps +git clone https://github.com/talos-gis/pywps-flask.git %talos_wps% +pushd "%~dp0" +cd /d %talos_wps% +git checkout talos_wps +git pull +popd + +mkdir %talos_wps_install_dir% +7za a %talos_wps_7z% %talos_wps%\* + +popd + +@echo done! + +if %1x==x pause \ No newline at end of file diff --git a/processes/__data__.py b/processes/__data__.py new file mode 100644 index 0000000..2a4a348 --- /dev/null +++ b/processes/__data__.py @@ -0,0 +1,7 @@ +import os + +__version__ = open(os.path.join(os.path.dirname(__file__), '../VERSION.txt')).read().rstrip() +__author__ = "Idan Miara" +__author_email__ = "idan@miara.com" +__license__ = "MIT" +__url__ = r"https://github.com/talos-gis" \ No newline at end of file diff --git a/processes/__init__.py b/processes/__init__.py index e69de29..524d25c 100644 --- a/processes/__init__.py +++ b/processes/__init__.py @@ -0,0 +1,57 @@ +from .sayhello import SayHello +from .ultimate_question import UltimateQuestion +from .sleep import Sleep +from .feature_count import FeatureCount +from .centroids import Centroids +from .buffer import Buffer +from .area import Area +from .bboxinout import Box +from .jsonprocess import TestJson + +from .info import GetInfo +from .ls import ls +from .invert import Invert +from .trans import Trans +from .xyz import XYZ +from .crop_color import GdalDem +from .rasval import RasterValue +from .viewshed import Viewshed +from .los import LOS +from .calc import Calc +from .tester import Tester +from .gdalinfo import GdalInfo +from .sandbox import Sandbox + +# For the process list on the home page +processes = [ + SayHello(), + UltimateQuestion(), + Sleep(), + FeatureCount(), + Centroids(), + Buffer(), + Area(), + Box(), + TestJson(), + + GetInfo(), + ls(), + Invert(), + Trans(), + XYZ(), + GdalDem(), + RasterValue(), + Viewshed(), + LOS(), + Calc(), + Tester(), + Sandbox(), + GdalInfo(), +] + +# For the process list on the home page +process_descriptor = {} +for process in processes: + abstract = process.abstract + identifier = process.identifier + process_descriptor[identifier] = abstract diff --git a/processes/buffer.py b/processes/buffer.py index 65a263c..ee7e8f1 100644 --- a/processes/buffer.py +++ b/processes/buffer.py @@ -1,6 +1,7 @@ +import tempfile from pywps import Process, LiteralInput, \ - ComplexInput, ComplexOutput, Format, FORMATS + ComplexInput, ComplexOutput, LiteralOutput, Format, FORMATS from pywps.validator.mode import MODE @@ -19,7 +20,8 @@ def __init__(self): supported_formats=[ Format('application/gml+xml') ] - )] + ), + LiteralOutput('r', 'input raster name', data_type='string')] super(Buffer, self).__init__( self._handler, @@ -38,43 +40,46 @@ def __init__(self): def _handler(self, request, response): from osgeo import ogr - inSource = ogr.Open(request.inputs['poly_in'][0].file) + filename = request.inputs['poly_in'][0].file + response.outputs['r'].data = filename + in_source = ogr.Open(filename) - inLayer = inSource.GetLayer() - out = inLayer.GetName() + '_buffer' + in_layer = in_source.GetLayer() + layer_name = in_layer.GetName() + '_buffer' + out_filename = tempfile.mktemp() # create output file driver = ogr.GetDriverByName('GML') - outSource = driver.CreateDataSource( - out, + out_source = driver.CreateDataSource( + out_filename, ["XSISCHEMAURI=\ http://schemas.opengis.net/gml/2.1.2/feature.xsd"]) - outLayer = outSource.CreateLayer(out, None, ogr.wkbUnknown) + out_layer = out_source.CreateLayer(layer_name, None, ogr.wkbUnknown) # for each feature - featureCount = inLayer.GetFeatureCount() + feature_count = in_layer.GetFeatureCount() index = 0 - while index < featureCount: + while index < feature_count: # get the geometry - inFeature = inLayer.GetNextFeature() - inGeometry = inFeature.GetGeometryRef() + in_feature = in_layer.GetNextFeature() + in_geometry = in_feature.GetGeometryRef() # make the buffer - buff = inGeometry.Buffer(float(request.inputs['buffer'][0].data)) + buff = in_geometry.Buffer(float(request.inputs['buffer'][0].data)) # create output feature to the file - outFeature = ogr.Feature(feature_def=outLayer.GetLayerDefn()) - outFeature.SetGeometryDirectly(buff) - outLayer.CreateFeature(outFeature) - outFeature.Destroy() # makes it crash when using debug + out_feature = ogr.Feature(feature_def=out_layer.GetLayerDefn()) + out_feature.SetGeometryDirectly(buff) + out_layer.CreateFeature(out_feature) + out_feature.Destroy() # makes it crash when using debug index += 1 - response.update_status('Buffering', 100*(index/featureCount)) + response.update_status('Buffering', 100*(index/feature_count)) - outSource.Destroy() + out_source.Destroy() response.outputs['buff_out'].output_format = FORMATS.GML - response.outputs['buff_out'].file = out + response.outputs['buff_out'].file = out_filename return response diff --git a/processes/calc.py b/processes/calc.py new file mode 100644 index 0000000..d9ebb9f --- /dev/null +++ b/processes/calc.py @@ -0,0 +1,129 @@ +import tempfile + +from backend.formats import czml_format +from gdalos import gdalos_util +from gdalos.calc import gdal_calc, gdal_to_czml, gdalos_combine +from gdalos import gdalos_main +from gdalos.rectangle import GeoRectangle +from processes import process_helper +from pywps import FORMATS +from pywps.app import Process +from pywps.app.Common import Metadata +from pywps.inout import ComplexOutput +from pywps.response.execute import ExecuteResponse +from .process_defaults import process_defaults, LiteralInputD, ComplexInputD, BoundingBoxInputD + + +class Calc(Process): + def __init__(self): + process_id = 'calc' + defaults = process_defaults(process_id) + inputs = [ + LiteralInputD(defaults, 'output_czml', 'make output as czml', data_type='boolean', + min_occurs=0, max_occurs=1, default=False), + LiteralInputD(defaults, 'output_tif', 'make output as tif', data_type='boolean', + min_occurs=0, max_occurs=1, default=True), + + ComplexInputD(defaults, 'r', 'input rasters', supported_formats=[FORMATS.GEOTIFF], + min_occurs=1, max_occurs=23, default=None), + LiteralInputD(defaults, 'a', 'alpha pattern', data_type='string', + min_occurs=1, max_occurs=1, default='1*({}>3)'), + + LiteralInputD(defaults, 'c', 'custom calc', data_type='string', + min_occurs=0, max_occurs=1, default=None), + LiteralInputD(defaults, 'f', 'function', data_type='string', + min_occurs=0, max_occurs=1, default='sum'), + LiteralInputD(defaults, 'o', 'operator', data_type='string', + min_occurs=0, max_occurs=1, default='+'), + + LiteralInputD(defaults, 'm', 'extent combine mode', data_type='integer', + min_occurs=1, max_occurs=1, default=2), + LiteralInputD(defaults, 'h', 'hide Nodata', data_type='boolean', + min_occurs=0, max_occurs=1, default=True), + ComplexInputD(defaults, 'color_palette', 'color palette', supported_formats=[FORMATS.TEXT], + min_occurs=0, max_occurs=1, default=None), + # ComplexInputD(defaults, 'cutline', 'input vector cutline', + # supported_formats=[FORMATS.GML], + # # crss=['EPSG:4326', ], metadata=[Metadata('EPSG.io', 'http://epsg.io/'), ], + # min_occurs=0, max_occurs=1, default=None), + # LiteralInputD(defaults, 'process_palette', 'put palette in czml description', data_type='integer', + # min_occurs=1, max_occurs=1, default=2), + BoundingBoxInputD(defaults, 'extent', 'extent BoundingBox', + crss=['EPSG:4326', ], metadata=[Metadata('EPSG.io', 'http://epsg.io/'), ], + min_occurs=0, max_occurs=1, default=None) + ] + outputs = [ + ComplexOutput('output', 'result raster', supported_formats=[FORMATS.GEOTIFF, czml_format]), + ] + + super().__init__( + self._handler, + identifier=process_id, + version='1.0.0', + title='gdal calc', + abstract='gdal calc', + inputs=inputs, + outputs=outputs, + metadata=[Metadata('raster')], + # profile='', + # store_supported=True, + # status_supported=True + ) + + def _handler(self, request, response: ExecuteResponse): + calc = process_helper.get_request_data(request.inputs, 'c') + func = process_helper.get_request_data(request.inputs, 'f') + operand = process_helper.get_request_data(request.inputs, 'o') + + if not (calc or func or operand): + raise Exception('Please provide one of: calc, func, operand') + + of: str = process_helper.get_request_data(request.inputs, 'of') + ext = gdalos_util.get_ext_by_of(of) + is_czml = ext == '.czml' + + # process_palette = request.inputs['process_palette'][0].data if output_czml else 0 + # cutline = process_helper.get_request_data(request.inputs, 'cutline') + extent = process_helper.get_request_data(request.inputs, 'extent') + if extent is not None: + # I'm not sure why the extent is in format miny, minx, maxy, maxx + extent = [float(x) for x in extent] + extent = GeoRectangle.from_min_max(extent[1], extent[3], extent[0], extent[2]) + else: + extent = request.inputs['m'][0].data + + alpha_pattern = process_helper.get_request_data(request.inputs, 'a') + hide_nodata = process_helper.get_request_data(request.inputs, 'h') + + output_filename = tempfile.mktemp(suffix=ext) + gdal_out_format = 'MEM' if is_czml else 'GTiff' + + files = [] + for r in request.inputs['r']: + _, src_ds = process_helper.open_ds_from_wps_input(r) + files.append(src_ds) + + kwargs = dict() + if calc is None: + if func: + calc, kwargs = gdalos_combine.make_calc_with_func(files, alpha_pattern, func, **kwargs) + else: + calc, kwargs = gdalos_combine.make_calc_with_operand(files, alpha_pattern, operand, **kwargs) + color_table = process_helper.get_color_table(request.inputs, 'color_palette') + if 'gdalos_version' not in dir(gdalos_main) or gdalos_main.gdalos_version < (0, 50): + kwargs['return_ds'] = gdal_out_format == 'MEM' + dst_ds = gdal_calc.Calc( + calc, outfile=output_filename, extent=extent, format=gdal_out_format, + color_table=color_table, hideNodata=hide_nodata, **kwargs) + + if output_filename is not None and dst_ds is not None: + gdal_to_czml.gdal_to_czml(dst_ds, name=output_filename, out_filename=output_filename) + + dst_ds = None # close ds + for i in range(len(files)): + files[i] = None + + response.outputs['output'].output_format = czml_format if is_czml else FORMATS.GEOTIFF + response.outputs['output'].file = output_filename + + return response diff --git a/processes/crop_color.py b/processes/crop_color.py new file mode 100644 index 0000000..32bb637 --- /dev/null +++ b/processes/crop_color.py @@ -0,0 +1,124 @@ +# todo: https://pywps.readthedocs.io/en/master/wps.html#wps +# * The input or output can also be result of any OGC OWS service. +# * how to select output format? +# wkt input + +# import owslib.wps +import tempfile +from pywps import FORMATS +from pywps.app import Process +from pywps.app.Common import Metadata +from pywps.inout import ComplexOutput, LiteralOutput +from pywps.response.execute import ExecuteResponse + +from gdalos.calc import gdal_dem_color_cutline +from backend.formats import czml_format +from gdalos import gdalos_util +from gdalos.rectangle import GeoRectangle +from .process_defaults import process_defaults, LiteralInputD, ComplexInputD, BoundingBoxInputD +from processes import process_helper +from gdalos.gdalos_color import ColorPalette + + +class GdalDem(Process): + def __init__(self): + process_id = 'crop_color' + defaults = process_defaults(process_id) + inputs = [ + LiteralInputD(defaults, 'of', 'output format (czml, gtiff)', data_type='string', + min_occurs=0, max_occurs=1, default='gtiff'), + LiteralInputD(defaults, 'output_czml', 'make output as czml', data_type='boolean', + min_occurs=0, max_occurs=1, default=None), + LiteralInputD(defaults, 'output_tif', 'make output as tif', data_type='boolean', + min_occurs=0, max_occurs=1, default=None), + + ComplexInputD(defaults, 'r', 'input raster', supported_formats=[FORMATS.GEOTIFF], + min_occurs=1, max_occurs=1, default=None), + ComplexInputD(defaults, 'color_palette', 'color palette', supported_formats=[FORMATS.TEXT], + min_occurs=0, max_occurs=1, default=None), + ComplexInputD(defaults, 'cutline', 'input vector cutline', + supported_formats=[FORMATS.GML], + # crss=['EPSG:4326', ], metadata=[Metadata('EPSG.io', 'http://epsg.io/'), ], + min_occurs=0, max_occurs=1, default=None), + LiteralInputD(defaults, 'process_palette', 'put palette in czml description', data_type='integer', + min_occurs=1, max_occurs=1, default=2), + BoundingBoxInputD(defaults, 'extent', 'extent BoundingBox', + crss=['EPSG:4326', ], metadata=[Metadata('EPSG.io', 'http://epsg.io/'), ], + min_occurs=0, max_occurs=1, default=None) + ] + outputs = [ + LiteralOutput('r', 'input raster name', data_type='string'), + ComplexOutput('output', 'result raster', supported_formats=[FORMATS.GEOTIFF, czml_format]), + ComplexOutput('tif', 'result as GeoTIFF', supported_formats=[FORMATS.GEOTIFF]), # backwards compatibility + ComplexOutput('czml', 'result as CZML', supported_formats=[czml_format]) # backwards compatibility + ] + + super().__init__( + self._handler, + identifier=process_id, + version='1.0.0', + title='crops to an extent and/or to a cutline polygon[s] and/or makes a color relief', + abstract='returns a color relief of the input raster inside the given extent or cutline polygon[s]', + inputs=inputs, + outputs=outputs, + metadata=[Metadata('raster')], + # profile='', + # store_supported=True, + # status_supported=True + ) + + def _handler(self, request, response: ExecuteResponse): + output_czml = process_helper.get_request_data(request.inputs, 'output_czml') + output_tif = process_helper.get_request_data(request.inputs, 'output_tif') + of: str = process_helper.get_request_data(request.inputs, 'of') + if output_czml: + of = 'czml' + ext = gdalos_util.get_ext_by_of(of) + is_czml = ext == '.czml' + output_czml = is_czml + output_tif = not is_czml + + if output_czml or output_tif: + process_palette = request.inputs['process_palette'][0].data if output_czml else 0 + cutline = request.inputs['cutline'][0].file if 'cutline' in request.inputs else None + # color_palette = request.inputs['color_palette'][0].file if 'color_palette' in request.inputs else None + color_palette = ColorPalette.from_string_list( + process_helper.get_request_data(request.inputs, 'color_palette', True)) + extent = request.inputs['extent'][0].data if 'extent' in request.inputs else None + if extent is not None: + # I'm not sure why the extent is in format miny, minx, maxy, maxx + extent = [float(x) for x in extent] + extent = GeoRectangle.from_min_max(extent[1], extent[3], extent[0], extent[2]) + + czml_output_filename = tempfile.mktemp(suffix=czml_format.extension) if output_czml else None + tif_output_filename = tempfile.mktemp(suffix=FORMATS.GEOTIFF.extension) if output_tif else None + output_filename = tif_output_filename or czml_output_filename + + # gdal_out_format = 'czml' if output_format.extension == '.czml' else 'GTiff' + # gdal_out_format = 'MEM' if is_czml else output_format + gdal_out_format = 'GTiff' if output_tif else 'MEM' + + raster_filename, ds = process_helper.open_ds_from_wps_input(request.inputs['r'][0]) + + gdal_dem_color_cutline.czml_gdaldem_crop_and_color( + ds=ds, + czml_output_filename=czml_output_filename, + out_filename=tif_output_filename, + extent=extent, cutline=cutline, + color_palette=color_palette, + process_palette=process_palette, + output_format=gdal_out_format) + + response.outputs['output'].output_format = czml_format if is_czml else FORMATS.GEOTIFF + response.outputs['output'].file = output_filename + + if output_tif: + response.outputs['tif'].output_format = FORMATS.GEOTIFF + response.outputs['tif'].file = tif_output_filename + if output_czml: + response.outputs['czml'].output_format = czml_format + response.outputs['czml'].file = czml_output_filename + + response.outputs['r'].data = raster_filename + # response.outputs['output'].uom = UOM('unity') + return response diff --git a/processes/gdalinfo.py b/processes/gdalinfo.py new file mode 100644 index 0000000..68a4d54 --- /dev/null +++ b/processes/gdalinfo.py @@ -0,0 +1,49 @@ +import subprocess +from pathlib import Path + +from pywps import FORMATS, UOM +from pywps.app import Process +from pywps.inout import LiteralOutput +from .process_defaults import process_defaults, LiteralInputD +from pywps.app.Common import Metadata +from pywps.response.execute import ExecuteResponse +from processes import process_helper +import processes.io_generator as iog + +import osgeo + + +class GdalInfo(Process): + def __init__(self): + process_id = 'gdalinfo' + defaults = process_defaults(process_id) + + inputs = iog.p(defaults, default='--formats') + + outputs = [LiteralOutput('output', 'gdalinfo output', data_type='string')] + + super().__init__( + self._handler, + identifier=process_id, + version='1.0.0', + title='gdalinfo', + abstract='gdalinfo', + profile='', + metadata=[Metadata('raster', 'vector')], + inputs=inputs, + outputs=outputs, + store_supported=True, + status_supported=True + ) + + def _handler(self, request, response: ExecuteResponse): + p = process_helper.get_input_data_array(request.inputs['p']) + + osgeo_path = Path(osgeo.__file__).parent + gdalinfo_path = osgeo_path / "gdalinfo" + output = subprocess.Popen([gdalinfo_path, *p], stdout=subprocess.PIPE).communicate()[0].decode("utf-8") + + response.outputs['output'].output_format = FORMATS.JSON + response.outputs['output'].data = output + + return response diff --git a/processes/info.py b/processes/info.py new file mode 100644 index 0000000..d4d4f5d --- /dev/null +++ b/processes/info.py @@ -0,0 +1,47 @@ +import importlib + +from osgeo import gdal +from pywps import Process, LiteralOutput +from .process_defaults import process_defaults + + +class GetInfo(Process): + def __init__(self): + process_id = 'info' + defaults = process_defaults(process_id) + self.modules = ('processes.__data__', 'talosgis', 'gdalos', 'osgeo.gdal') + outputs = [ + LiteralOutput('output', 'service version', data_type='string'), + LiteralOutput('gdal_drv', 'gdal drivers', data_type='string'), + LiteralOutput('gdal_desc', 'gdal drivers descriptions', data_type='string'), + *[LiteralOutput(module, f'{module} version', data_type='string') for module in self.modules] + ] + + super(GetInfo, self).__init__( + self._handler, + identifier=process_id, + title='Service info', + abstract='Returns service info', + version='1.0.0', + # inputs=inputs, + outputs=outputs, + store_supported=True, + status_supported=True + ) + + def _handler(self, request, response): + versions = {} + for m in self.modules: + version = importlib.import_module(m).__version__ + versions[m] = version + response.outputs[m].data = version + response.outputs['output'].data = '; '.join([f'{m}: {versions[m]}' for m in self.modules]) + response.outputs['gdal_drv'].data, response.outputs['gdal_desc'].data = gdal_formats() + return response + + +def gdal_formats(): + driver_list = [gdal.GetDriver(i) for i in range(gdal.GetDriverCount())] + name_list = [drv.ShortName for drv in driver_list] + desc_list = [drv.GetDescription() for drv in driver_list] + return name_list, desc_list diff --git a/processes/invert.py b/processes/invert.py new file mode 100644 index 0000000..b8b6824 --- /dev/null +++ b/processes/invert.py @@ -0,0 +1,67 @@ +from pywps import FORMATS +from pywps.app import Process +from pywps.inout import ComplexInput, ComplexOutput +from pywps.app.Common import Metadata +from pywps.response.execute import ExecuteResponse +from .process_defaults import process_defaults, LiteralInputD, ComplexInputD, BoundingBoxInputD +import numpy as np +from osgeo import gdal + + +class Invert(Process): + def __init__(self): + process_id = 'invert' + defaults = process_defaults(process_id) + inputs = [ + ComplexInputD(defaults, 'A', 'input_raster', supported_formats=[FORMATS.GEOTIFF]) + ] + outputs = [ + ComplexOutput('output', 'result raster', supported_formats=[FORMATS.GEOTIFF]) + ] + + super().__init__( + self._handler, + identifier=process_id, + version='1.0.0', + title='invert the values of the raster', + abstract='invert', + profile='', + metadata=[Metadata('raster')], + inputs=inputs, + outputs=outputs, + store_supported=True, + status_supported=True + ) + + def _handler(self, request, response: ExecuteResponse): + + raster_filename = request.inputs['A'][0].file + + s_ds: gdal.Dataset = gdal.Open(raster_filename, gdal.GA_ReadOnly) + s_band: gdal.Band = s_ds.GetRasterBand(1) + (s_min, s_max, *_) = s_band.GetStatistics(False, True) + # todo GetVirtualMemArray + s_ndv = s_band.GetNoDataValue() + s_array = s_band.ReadAsArray() + s_array = np.ma.masked_equal(s_array, s_ndv) + + d_array = s_max + s_min - s_array + + d_path = raster_filename + '_inverse' + d_ds: gdal.Dataset = gdal.GetDriverByName('GTIFF').Create(d_path, s_ds.RasterXSize, s_ds.RasterYSize, 1, + s_band.DataType) + d_ds.SetProjection(s_ds.GetProjection()) + d_ds.SetGeoTransform(s_ds.GetGeoTransform()) + + d_band = d_ds.GetRasterBand(1) + d_band.SetNoDataValue(s_ndv) + d_band.WriteArray(d_array) + d_band.FlushCache() + + del d_band + del d_ds + + response.outputs['output'].output_format = FORMATS.GEOTIFF + response.outputs['output'].file = d_path + + return response diff --git a/processes/io_generator.py b/processes/io_generator.py new file mode 100644 index 0000000..ea82288 --- /dev/null +++ b/processes/io_generator.py @@ -0,0 +1,354 @@ +from backend.formats import czml_format +from gdalos import util as gdalos_base +from gdalos.gdalos_selector import DataSetSelector +from gdalos.viewshed import radio_params +from gdalos.viewshed.radio_params import RadioParams +from gdalos.viewshed.viewshed_calc import ViewshedBackend +from gdalos.viewshed.viewshed_params import viewshed_defaults, atmospheric_refraction_coeff +from pywps import FORMATS, UOM +from pywps.app.Common import Metadata +from pywps.exceptions import MissingParameterValue +from pywps.inout import LiteralOutput, ComplexOutput +from . import process_helper +from .process_defaults import LiteralInputD, ComplexInputD, BoundingBoxInputD + +mm = dict(min_occurs=1, max_occurs=None) +mm0 = dict(min_occurs=0, max_occurs=None) +mmm = dict(data_type='float', uoms=[UOM('metre')], **mm) +mmm0 = dict(data_type='float', uoms=[UOM('metre')], **mm0) +dmm = dict(data_type='float', uoms=[UOM('degree')], **mm) + + +# 254 is the max possible values for unique function. for sum it's not really limited + + +def io_crs(defaults): + return [ + LiteralInputD(defaults, 'out_crs', 'output raster crs', data_type='string', default=None, min_occurs=0, + max_occurs=1), + LiteralInputD(defaults, 'in_crs', 'observer input crs', data_type='string', default=None, min_occurs=0, + max_occurs=1), + ] + + +def of_raster(defaults): + return [ + LiteralInputD(defaults, 'of', 'output raster format (czml, gtiff)', data_type='string', + min_occurs=0, max_occurs=1, default='gtiff'), + ] + + +def of_pointcloud(defaults): + return [ + LiteralInputD(defaults, 'of', 'output vector format (xyz/json)', data_type='string', + min_occurs=0, max_occurs=1, default=None), + ] + + +def p(defaults, default): + return [ + LiteralInputD(defaults, 'p', 'parameters', data_type='string', min_occurs=1, max_occurs=None, default=default), + ] + +def raster_input(defaults): + return [ + # ComplexInputD(defaults, 'r', 'input raster', supported_formats=[FORMATS.GEOTIFF], min_occurs=1, max_occurs=1), + LiteralInputD(defaults, 'r', 'input raster', data_type='string', min_occurs=1, max_occurs=1), + LiteralInputD(defaults, 'bi', 'band index', data_type='positiveInteger', default=1, min_occurs=0, + max_occurs=1), + LiteralInputD(defaults, 'ovr', 'input raster ovr', data_type='integer', default=0, min_occurs=0, + max_occurs=1), + LiteralInputD(defaults, 'co', 'input raster creation options', data_type='string', min_occurs=0, + max_occurs=1), + ] + + +def resolution_output(defaults): + return [ + LiteralInputD(defaults, 'out_res', 'requested resolution of the output', **mmm0) + ] + + +def raster_ranges(defaults): + return [ + LiteralInputD(defaults, 'min_r', 'Minimum visibility range/radius/distance', default=0, **mmm), + LiteralInputD(defaults, 'max_r', 'Maximum visibility range/radius/distance', **mmm), + LiteralInputD(defaults, 'min_r_shave', 'ignore DTM before Minimum range', default=False, + data_type='boolean', **mm), + LiteralInputD(defaults, 'max_r_slant', 'Use Slant Range as Max Range (instead of ground range)', + data_type='boolean', default=True, **mm), + ] + + +def xy(defaults): + return [ + LiteralInputD(defaults, 'x', 'x or longitude or pixel', data_type='float', min_occurs=1, max_occurs=None, + uoms=[UOM('metre')]), + LiteralInputD(defaults, 'y', 'y or latitude or line', data_type='float', min_occurs=1, max_occurs=None, + uoms=[UOM('metre')]), + ] + + +# https://en.wikipedia.org/wiki/Height_above_ground_level MSL/AGL +def observer(defaults, xy, z, msl): + inputs = [] + if xy: + inputs.extend([ + # x,y in the given input-CRS + LiteralInputD(defaults, 'ox', 'observer X/longitude', **mmm), + LiteralInputD(defaults, 'oy', 'observer Y/latitude', **mmm), + ]) + if z: + inputs.extend([ + LiteralInputD(defaults, 'oz', 'observer height/altitude/elevation', **mmm0), + ]) + if msl: + inputs.extend([ + LiteralInputD(defaults, 'omsl', 'observer height mode MSL(True) / AGL(False)', default=False, + data_type='boolean', **mm), + ]) + return inputs + + +def target(defaults, xy, z, msl): + inputs = [] + if xy: + inputs.extend([ + # x,y in the given input-CRS + LiteralInputD(defaults, 'tx', 'target X/longitude', **mmm), + LiteralInputD(defaults, 'ty', 'target Y/latitude', **mmm), + ]) + if z: + inputs.extend([ + LiteralInputD(defaults, 'tz', 'target height/altitude/elevation', **mmm0), + ]) + if msl: + inputs.extend([ + LiteralInputD(defaults, 'tmsl', 'target height mode MSL(True) / AGL(False)', default=False, + data_type='boolean', **mm), + ]) + return inputs + + +def angles(defaults): + return [ + LiteralInputD(defaults, 'azimuth', 'horizontal azimuth direction', default=0, **dmm), + LiteralInputD(defaults, 'h_aperture', 'horizontal aperture', default=360, **dmm), + LiteralInputD(defaults, 'elevation', 'vertical elevation direction', default=0, **dmm), + LiteralInputD(defaults, 'v_aperture', 'vertical aperture', default=180, **dmm), + ] + + +def viewshed_values(defaults): + return [ + # optional values + LiteralInputD(defaults, 'vv', 'visible_value', default=viewshed_defaults['vv'], **mmm), + LiteralInputD(defaults, 'iv', 'invisible_value', default=viewshed_defaults['iv'], **mmm), + LiteralInputD(defaults, 'ov', 'out_of_bounds_value', default=viewshed_defaults['ov'], **mmm), + LiteralInputD(defaults, 'ndv', 'nodata_value', default=viewshed_defaults['ndv'], **mmm), + ] + + +def slice(defaults): + return [ + LiteralInputD(defaults, 'vps', 'Use only the given slice of input parameters set', + default=None, data_type='string', min_occurs=0, max_occurs=1), + ] + + +def backend(defaults): + return [ + # advanced parameters + LiteralInputD(defaults, 'backend', 'Calculation backend to use', + default=None, data_type='string', **mm0), + ] + + +def refraction(defaults): + return [ + LiteralInputD(defaults, 'refraction_coeff', 'atmospheric refraction correction coefficient', + default=atmospheric_refraction_coeff, data_type='float', **mm), # was: 1-cc + ] + + +def mode(defaults, default=None): + return [ + LiteralInputD(defaults, 'mode', 'calculation mode', default=default, data_type='string', **mm0), + ] + + +def color_palette(defaults): + return [ + # color + ComplexInputD(defaults, 'color_palette', 'color palette', supported_formats=[FORMATS.TEXT], + min_occurs=0, max_occurs=1, default=None), + LiteralInputD(defaults, 'discrete_mode', 'discrete mode', default='interp', data_type='string', **mm), + ] + + +def extent(defaults): + return [ + # output extent definition + LiteralInputD(defaults, 'extent_c', 'extent combine mode 2:union/3:intersection', data_type='integer', + min_occurs=1, max_occurs=1, default=2), # was: m + BoundingBoxInputD(defaults, 'extent', 'extent BoundingBox', + crss=['EPSG:4326', ], metadata=[Metadata('EPSG.io', 'http://epsg.io/'), ], + min_occurs=0, max_occurs=1, default=None), + ComplexInputD(defaults, 'cutline', 'output vector cutline', + supported_formats=[FORMATS.GML], + # crss=['EPSG:4326', ], metadata=[Metadata('EPSG.io', 'http://epsg.io/'), ], + min_occurs=0, max_occurs=1, default=None), + ] + + +def operation(defaults): + return [ + # combine calc modes + # 254 is the max possible values for unique function. for sum it's not really limited + LiteralInputD(defaults, 'o', 'operation viewshed/max/count/count_z/unique', data_type='string', + min_occurs=0, max_occurs=1, default=None), + ] + + +def xy_fill(defaults): + return [ + LiteralInputD(defaults, 'xy_fill', 'zip/zip_cycle/product', default=gdalos_base.FillMode.zip_cycle, + data_type='string', min_occurs=1, max_occurs=1) + ] + + +def ot_fill(defaults): + return [ + LiteralInputD(defaults, 'ot_fill', 'zip/zip_cycle/product', default=gdalos_base.FillMode.zip_cycle, + data_type='string', min_occurs=1, max_occurs=1) + ] + + +def mock(defaults): + return [ + + LiteralInputD(defaults, 'mock', 'if set then zeros will be returned instread of actual results', + data_type='boolean', default=False, min_occurs=1, max_occurs=1), + ] + + +def radio(defaults): + return [ + LiteralInputD(defaults, 'frequency', 'radio: Transmitter frequency in MHz. Range: 1.0 to 40000.0 MHz', + data_type='float', **mm0), + LiteralInputD(defaults, 'KFactor', 'radio: KFactor', + data_type='float', default=0, **mm0), + LiteralInputD(defaults, 'polarity', 'radio: Transmitter antenna polarization (Horizontal or Vertical)', + data_type='string', **mm0), + LiteralInputD(defaults, 'calc_type', 'radio: calculation output type', + data_type='string', default=radio_params.RadioCalcType.PathLoss.name, **mm0), + + # Radio: Earth surface parameters + LiteralInputD(defaults, 'refractivity', 'radio: Surface refractivity in N-units. Range: 200.0 to 450.0 N', + data_type='float', default=None, **mm0), + LiteralInputD(defaults, 'conductivity', + 'radio: Conductivity of earth surface Siemans per meter. Range: 0.00001 to 100.0 S/m', + data_type='float', default=None, **mm0), + LiteralInputD(defaults, 'permittivity', + 'radio: Relative permittivity of earth surface. Range: 1.0 to 100.0', + data_type='float', default=None, **mm0), + LiteralInputD(defaults, 'humidity', + 'radio: Surface humidity at the transmitter site in grams per cubic meter. ' + 'Range: 0.0 to 110.0 in g/m^3', + data_type='float', default=None, **mm0), + + LiteralInputD(defaults, 'power_diff', + 'radio: power difference = BroadcastPower - MinPower. ' + 'Only relevant for PowerReminder calculation. ' + 'PowerReminder = power_diff - path_loss', data_type='float', **mm0), + LiteralInputD(defaults, 'fill_center', + 'radio: fill missing samples data with FreeSpace calculation, ' + 'Sometimes when the distance too short the radio calculation returns invalid value. ' + 'When setting this value to True FreeSpace loss will be calculated instead.', + data_type='boolean', default=False, **mm0), + LiteralInputD(defaults, 'profile_extension', 'radio: allow use profile extension whenever is possible', + data_type='boolean', default=True, **mm0), + ] + + +def fake_raster(defaults): + return [ + ComplexInputD(defaults, 'fr', 'fake input rasters (for debugging)', supported_formats=[FORMATS.GEOTIFF], + min_occurs=0, max_occurs=23, default=None), + ] + + +def skip_src_dst_nodata(defaults): + return [ + LiteralInputD(defaults, 'skip_nodata', 'skip NoData values', + data_type='boolean', min_occurs=1, max_occurs=1, default=True), + LiteralInputD(defaults, 'src_nodata', 'override source NoData values', + data_type='float', min_occurs=0, max_occurs=1), + LiteralInputD(defaults, 'dst_nodata', 'replace source NoData values with the given destination NoData values', + data_type='float', min_occurs=0, max_occurs=1), + ] + + +def output_r(name='r'): + return [ + LiteralOutput(name, 'input raster name', data_type='string'), + ] + + +def output_value(names): + return list(LiteralOutput(f'{x}', f'{x} values', data_type=None) for x in names) + + +def output_output(is_output_raster, name='output'): + return [ + ComplexOutput(name, 'calculation result', + supported_formats=[FORMATS.GEOTIFF, czml_format] if is_output_raster else [FORMATS.TEXT]), + ] + + +def get_io_crs(request_inputs): + in_coords_srs = process_helper.get_request_data(request_inputs, 'in_crs') + if in_coords_srs == '': + in_coords_srs = None + out_crs = process_helper.get_request_data(request_inputs, 'out_crs') + if out_crs == '': + out_crs = None + return in_coords_srs, out_crs + + +def get_input_raster(request_inputs): + ovr_idx = request_inputs['ovr'][0].data + # raster_filename, input_ds = process_helper.open_ds_from_wps_input(request_inputs['r'][0], ovr_idx=ovr_idx) + raster_filename = process_helper.get_request_data(request_inputs, 'r') + bi = request_inputs['bi'][0].data + + co = None + if 'co' in request_inputs: + co = [] + for coi in request_inputs['co']: + creation_option: str = coi.data + sep_index = creation_option.find('=') + if sep_index == -1: + raise Exception(f'creation option {creation_option} unsupported') + co.append(creation_option) + + return raster_filename, bi, ovr_idx, co + + +def get_vp(request_inputs, vp_class): + backend = process_helper.get_request_data(request_inputs, 'backend') + vp_arrays_dict = process_helper.get_arrays_dict(request_inputs, gdalos_base.get_all_slots(vp_class)) + + if 'radio' in backend: + backend = ViewshedBackend.talos + radio_arrays_dict = process_helper.get_arrays_dict(request_inputs, gdalos_base.get_all_slots(RadioParams)) + for k, v in radio_arrays_dict.items(): + if v is None: + raise MissingParameterValue(k, k) + vp_arrays_dict['radio_parameters'] = radio_arrays_dict + + return backend, vp_arrays_dict + + +def get_input_file(raster_filename, use_data_selector=True): + return None if not raster_filename else DataSetSelector(raster_filename) if use_data_selector else raster_filename diff --git a/processes/jsonprocess.py b/processes/jsonprocess.py index 70127dd..9ae5abf 100644 --- a/processes/jsonprocess.py +++ b/processes/jsonprocess.py @@ -4,7 +4,7 @@ class TestJson(Process): def __init__(self): inputs = [LiteralInput('name', 'Input name', data_type='string')] - outputs = [ComplexOutput('out', 'Referenced Output', + outputs = [ComplexOutput('output', 'Referenced Output', supported_formats=[Format('application/geojson')])] super(TestJson, self).__init__( @@ -21,7 +21,7 @@ def __init__(self): def _handler(self, request, response): data = json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') out_bytes = json.dumps(data, indent=2) - response.outputs['out'].output_format = 'application/json' - response.outputs['out'].data = out_bytes + response.outputs['output'].output_format = 'application/json' + response.outputs['output'].data = out_bytes return response diff --git a/processes/los.py b/processes/los.py new file mode 100644 index 0000000..240e731 --- /dev/null +++ b/processes/los.py @@ -0,0 +1,86 @@ +import tempfile + +from pywps import FORMATS +from pywps.app import Process +from gdalos.viewshed.radio_params import RadioCalcType +from .process_defaults import process_defaults +from pywps.app.Common import Metadata +from pywps.response.execute import ExecuteResponse +from processes import process_helper +from gdalos.viewshed.viewshed_params import MultiPointParams +from gdalos.gdalos_main import gdalos_util +from gdalos.viewshed.viewshed_calc import los_calc +import processes.io_generator as iog + + +class LOS(Process): + def __init__(self): + process_id = 'los' + defaults = process_defaults(process_id) + + inputs = \ + iog.io_crs(defaults) + \ + iog.of_pointcloud(defaults) + \ + iog.raster_input(defaults) + \ + iog.observer(defaults, xy=True, z=True, msl=True) + \ + iog.target(defaults, xy=True, z=True, msl=True) + \ + iog.backend(defaults) + \ + iog.refraction(defaults) + \ + iog.mode(defaults, default=str(RadioCalcType.PathLoss)) + \ + iog.radio(defaults) + \ + iog.xy_fill(defaults) + \ + iog.ot_fill(defaults) + \ + iog.mock(defaults) + + outputs = iog.output_r() + \ + iog.output_value(['values']) + \ + iog.output_output(is_output_raster=False) + + + super().__init__( + self._handler, + identifier=process_id, + version='1.0', + title='LOS/Radio Multi Point Analysis', + abstract='Runs Line Of Sight or Radio Analysis on multiple point pairs', + profile='', + metadata=[Metadata('raster')], + inputs=inputs, + outputs=outputs, + store_supported=True, + status_supported=True + ) + + def _handler(self, request, response: ExecuteResponse): + of = process_helper.get_request_data(request.inputs, 'of') + if of is None: + output_filename = None + else: + of = str(of).lower() + ext = gdalos_util.get_ext_by_of(of) + output_filename = tempfile.mktemp(suffix=ext) + + raster_filename, bi, ovr_idx, co = iog.get_input_raster(request.inputs) + + in_coords_srs, out_crs = iog.get_io_crs(request.inputs) + mock = process_helper.get_request_data(request.inputs, 'mock') + + backend, vp_arrays_dict = iog.get_vp(request.inputs, MultiPointParams) + + input_file = iog.get_input_file(raster_filename, use_data_selector=True) + + results = los_calc( + input_filename=input_file, ovr_idx=ovr_idx, bi=bi, backend=backend, + output_filename=output_filename, co=co, of=of, + vp=vp_arrays_dict, + in_coords_srs=in_coords_srs, out_crs=out_crs, mock=mock) + + response.outputs['r'].data = raster_filename + if output_filename is None: + response.outputs['values'].output_format = FORMATS.JSON + response.outputs['values'].data = results + else: + response.outputs['output'].output_format = FORMATS.TEXT + response.outputs['output'].file = output_filename + + return response diff --git a/processes/ls.py b/processes/ls.py new file mode 100644 index 0000000..5490b6a --- /dev/null +++ b/processes/ls.py @@ -0,0 +1,44 @@ +import glob +from pywps import Process, LiteralInput, LiteralOutput, UOM +from .process_defaults import process_defaults, LiteralInputD +from pathlib import Path + + +class ls(Process): + def __init__(self): + process_id = 'ls' + defaults = process_defaults(process_id) + inputs = [ + LiteralInputD(defaults, 'dir', 'argument', data_type='string', min_occurs=0, default='./sample/maps'), + LiteralInputD(defaults, 'pattern', 'argument', data_type='string', min_occurs=0, default='*.tif'), + LiteralInputD(defaults, 'r', 'recursive', data_type='boolean', min_occurs=0, max_occurs=1, default=False), + ] + outputs = [LiteralOutput('output', + 'Output response', data_type='string')] + + super(ls, self).__init__( + self._handler, + identifier=process_id, + title='Process ls', + abstract='Returns a the output of ls to the provided dir', + version='1.0.0', + inputs=inputs, + outputs=outputs, + store_supported=True, + status_supported=True + ) + + def _handler(self, request, response): + files = [] + r = request.inputs['r'][0].data + for d in request.inputs['dir']: + dir = Path(d.data) + for p in request.inputs['pattern']: + pattern = Path(p.data) + pattern = dir / pattern + files.extend([f for f in glob.glob(str(pattern), recursive=r)]) + files = list(files) + response.outputs['output'].data = str(files) + + response.outputs['output'].uom = UOM('unity') + return response diff --git a/processes/process_defaults.py b/processes/process_defaults.py new file mode 100644 index 0000000..a4f1d3c --- /dev/null +++ b/processes/process_defaults.py @@ -0,0 +1,35 @@ +import os +import yaml +from pywps import LiteralInput, ComplexInput, BoundingBoxInput + + +def process_defaults(id, filename="./config/process_defaults.yaml"): + if 'd' not in process_defaults.__dict__: + process_defaults.d = dict() + if os.path.exists(filename): + try: + stream = open(filename, 'r') + process_defaults.d = yaml.safe_load(stream) + except yaml.scanner.ScannerError as err: + print('process defaults cannot be loaded {}'.format(err)) + except: + pass + return process_defaults.d.get(id, dict()) + + +class LiteralInputD(LiteralInput): + def __init__(self, defaults, identifier, *args, **kwargs): + kwargs['default'] = defaults.get(identifier, kwargs.get('default', None)) + super(LiteralInputD, self).__init__(identifier, *args, **kwargs) + + +class ComplexInputD(ComplexInput): + def __init__(self, defaults, identifier, *args, **kwargs): + kwargs['default'] = defaults.get(identifier, kwargs.get('default', None)) + super(ComplexInputD, self).__init__(identifier, *args, **kwargs) + + +class BoundingBoxInputD(BoundingBoxInput): + def __init__(self, defaults, identifier, *args, **kwargs): + kwargs['default'] = defaults.get(identifier, kwargs.get('default', None)) + super(BoundingBoxInputD, self).__init__(identifier, *args, **kwargs) diff --git a/processes/process_helper.py b/processes/process_helper.py new file mode 100644 index 0000000..3db7240 --- /dev/null +++ b/processes/process_helper.py @@ -0,0 +1,40 @@ +import copy +from typing import List + +from gdalos import gdalos_util + + +def get_request_data(request_input, name, get_file: bool = False, index=0): + # result = request_input[name][index].data if name in request_input else None + if name not in request_input: + return None + result = request_input[name][index] + result = result.file if get_file else result.data + if result == 'None': + result = None # todo: is this a bug? + return result + + +def get_input_data_array(request_input) -> List: + return [x.data for x in request_input] + + +def get_arrays_dict(request_inputs, params) -> dict: + return {k: get_input_data_array(request_inputs[k]) if k in request_inputs else None for k in params} + + +def open_ds_from_wps_input(request_input, **kwargs): + # ds: gdal.Dataset + raster_filename = request_input.file + try: + ds = gdalos_util.open_ds(raster_filename, **kwargs) + except: + ds = None + if ds is None: + raster_filename = request_input.data + ds = gdalos_util.open_ds(raster_filename, **kwargs) + if ds is None: + raise Exception('cannot open file {}'.format(raster_filename)) + return raster_filename, ds + + diff --git a/processes/rasval.py b/processes/rasval.py new file mode 100644 index 0000000..adb2be6 --- /dev/null +++ b/processes/rasval.py @@ -0,0 +1,76 @@ +from osgeo import gdal + +from pywps import FORMATS, UOM +from pywps.app import Process +from pywps.inout import LiteralOutput +from .process_defaults import process_defaults, LiteralInputD +from pywps.app.Common import Metadata +from pywps.response.execute import ExecuteResponse +from gdalos.calc import get_pixel_from_raster +from processes import process_helper +import processes.io_generator as iog + + +class RasterValue(Process): + def __init__(self): + process_id = 'ras_val' + defaults = process_defaults(process_id) + + inputs = \ + iog.io_crs(defaults) + \ + iog.of_pointcloud(defaults) + \ + iog.raster_input(defaults) + \ + iog.xy(defaults) + \ + [ + LiteralInputD(defaults, 'c', 'coordinate kind: ll/xy/pl', data_type='string', min_occurs=1, max_occurs=1, default='ll'), + LiteralInputD(defaults, 'interpolate', 'interpolate ', data_type='boolean', min_occurs=1, max_occurs=1, default=True), + ] + outputs = [LiteralOutput('output', 'raster values at the requested coordinates', data_type=None)] + + super().__init__( + self._handler, + identifier=process_id, + version='1.1.0', + title='raster values', + abstract='get raster values at given coordinates', + profile='', + metadata=[Metadata('raster')], + inputs=inputs, + outputs=outputs, + store_supported=True, + status_supported=True + ) + + def _handler(self, request, response: ExecuteResponse): + raster_filename, ds = process_helper.open_ds_from_wps_input(request.inputs['r'][0]) + + band: gdal.Band = ds.GetRasterBand(request.inputs['bi'][0].data) + if band is None: + raise Exception('band number out of range') + + c = request.inputs['c'][0].data.lower() if 'c' in request.inputs else None + if c is None: + srs = None + else: + if c == 'pl': + srs = None + elif c == 'xy': + srs = False + elif c == 'll': + srs = True + else: + raise Exception('Unknown xy format {}'.format(c)) + + x = process_helper.get_input_data_array(request.inputs['x']) + y = process_helper.get_input_data_array(request.inputs['y']) + + if len(x) != len(y): + raise Exception('length(x)={} is different from length(y)={}'.format(len(x), len(y))) + + values = get_pixel_from_raster.get_pixel_from_raster(ds, x, y, srs) + del ds + + response.outputs['output'].output_format = FORMATS.JSON + response.outputs['output'].data = values + + return response diff --git a/processes/sandbox.py b/processes/sandbox.py new file mode 100644 index 0000000..ed12fd2 --- /dev/null +++ b/processes/sandbox.py @@ -0,0 +1,45 @@ +import json + +from pywps import Process, LiteralInput, LiteralOutput, FORMATS + + +class Sandbox(Process): + def __init__(self): + process_id = 'sandbox' + inputs = [ + LiteralInput('name', 'Input name', data_type='string', default='World'), + LiteralInput('age', 'Input number', data_type='float', default=42), + ] + outputs = [LiteralOutput('output', 'Output response', data_type=None)] + + super(Sandbox, self).__init__( + self._handler, + identifier=process_id, + title='Process Sandbox', + abstract='Returns a literal string output', + version='1.0', + inputs=inputs, + outputs=outputs, + store_supported=True, + status_supported=True + ) + + def _handler(self, request, response): + name = request.inputs['name'][0].data + num = request.inputs['age'][0].data + response.outputs['output'].output_format = FORMATS.TEXT + response.outputs['output'].data = str(Person(name, num)) + return response + + +class Person: + def __init__(self, person_name, person_age): + self.name = person_name + self.age = person_age + + def __str__(self): + return f'Person name is {self.name} and age is {self.age}' + + def __repr__(self): + return f'Person(name={self.name}, age={self.age})' + diff --git a/processes/sayhello.py b/processes/sayhello.py index fdc94da..f61103e 100644 --- a/processes/sayhello.py +++ b/processes/sayhello.py @@ -5,17 +5,17 @@ class SayHello(Process): def __init__(self): - inputs = [LiteralInput('name', 'Input name', data_type='string')] - outputs = [LiteralOutput('response', - 'Output response', data_type='string')] + process_id = 'say_hello' + inputs = [LiteralInput('name', 'Input name', data_type='string', default='World')] + outputs = [LiteralOutput('output', 'Output response', data_type='string')] super(SayHello, self).__init__( self._handler, - identifier='say_hello', + identifier=process_id, title='Process Say Hello', abstract='Returns a literal string output\ with Hello plus the inputed name', - version='1.3.3.7', + version='1.3.3.8', inputs=inputs, outputs=outputs, store_supported=True, @@ -23,7 +23,7 @@ def __init__(self): ) def _handler(self, request, response): - response.outputs['response'].data = 'Hello ' + \ + response.outputs['output'].data = 'Hello ' + \ request.inputs['name'][0].data - response.outputs['response'].uom = UOM('unity') + response.outputs['output'].uom = UOM('unity') return response diff --git a/processes/sleep.py b/processes/sleep.py index 9975af2..2a4d6f3 100644 --- a/processes/sleep.py +++ b/processes/sleep.py @@ -27,12 +27,11 @@ class Sleep(Process): SUCCESS_MESSAGE = 'done sleeping' def __init__(self): - inputs = [LiteralInput('delay', - 'Delay between every update', - data_type='float')] - outputs = [LiteralOutput('sleep_output', - 'Sleep Output', - data_type='string')] + inputs = [LiteralInput('delay', 'Delay between every update', data_type='float', default=10), + LiteralInput('times', 'Update times', data_type='positiveInteger', default=5)] + outputs = [LiteralOutput('sleep_output', self.SUCCESS_MESSAGE+' Output', data_type='string'), + LiteralOutput('time', 'Float Output response', data_type='float'), + LiteralOutput('output', 'String Output response', data_type='string')] super(Sleep, self).__init__( self._handler, @@ -52,22 +51,18 @@ def __init__(self): def _handler(self, request, response): import time - sleep_delay = request.inputs['delay'][0].data - if sleep_delay: - sleep_delay = float(sleep_delay) - else: - sleep_delay = 10 + t = time.time() + sleep_delay = float(request.inputs['delay'][0].data) + sleep_times = int(request.inputs['times'][0].data) + + for i in range(sleep_times): + response.update_status('PyWPS Process started. Waiting...', 100*i/sleep_times) + time.sleep(sleep_delay) + t = time.time() - t - time.sleep(sleep_delay) - response.update_status('PyWPS Process started. Waiting...', 20) - time.sleep(sleep_delay) - response.update_status('PyWPS Process started. Waiting...', 40) - time.sleep(sleep_delay) - response.update_status('PyWPS Process started. Waiting...', 60) - time.sleep(sleep_delay) - response.update_status('PyWPS Process started. Waiting...', 80) - time.sleep(sleep_delay) response.outputs['sleep_output'].data = self.SUCCESS_MESSAGE + response.outputs['time'].data = t + response.outputs['output'].data = 'I slept for {} sleep_delay'.format(t) return response @@ -85,5 +80,6 @@ def main(): assert response.outputs["sleep_output"].data == sleep.SUCCESS_MESSAGE print("All good!") + if __name__ == "__main__": main() diff --git a/processes/tester.py b/processes/tester.py new file mode 100644 index 0000000..455a858 --- /dev/null +++ b/processes/tester.py @@ -0,0 +1,54 @@ +from pywps import Process, LiteralInput, \ + ComplexInput, ComplexOutput, LiteralOutput, Format, FORMATS +from pywps import FORMATS, UOM + +from pywps.validator.mode import MODE + +from .process_defaults import process_defaults, LiteralInputD, ComplexInputD, BoundingBoxInputD + +__author__ = 'Idan Miara' + + +class Tester(Process): + def __init__(self): + process_id = 'tester' + defaults = process_defaults(process_id) + inputs = [ + LiteralInputD(defaults, 'name', 'tell me your name', data_type='string', + min_occurs=0, max_occurs=1, default='secret'), + ComplexInput('r', 'Input gtiff file', supported_formats=[FORMATS.GEOTIFF], mode=MODE.STRICT)] + outputs = [ + LiteralOutput('name', 'your name', data_type='string'), + LiteralOutput('r', 'input raster name', data_type='string'), + LiteralOutput('pwd', 'pwd', data_type='string'), + LiteralOutput('gt', 'gt', data_type='string'), + ] + + super(Tester, self).__init__( + self._handler, + identifier=process_id, + version='0.1', + title="Tester process", + abstract="""Tests using the GDAL library""", + profile='', + inputs=inputs, + outputs=outputs, + store_supported=True, + status_supported=True + ) + + def _handler(self, request, response): + import os + from osgeo import gdal + + response.outputs['name'].data = request.inputs['name'][0].data + + response.outputs['pwd'].data = os.getcwd() + + filename = request.inputs['r'][0].file + response.outputs['r'].data = filename + + ds = gdal.Open(str(filename), gdal.GA_ReadOnly) + response.outputs['gt'].data = str(ds.GetGeoTransform()) + + return response diff --git a/processes/trans.py b/processes/trans.py new file mode 100644 index 0000000..b3ba558 --- /dev/null +++ b/processes/trans.py @@ -0,0 +1,54 @@ +import tempfile + +import processes.io_generator as iog +from gdalos.gdalos_main import gdalos_trans +from gdalos.gdalos_main import gdalos_util +from gdalos.gdal2xyz import gdal2xyz +from processes import process_helper +from pywps import FORMATS +from pywps.app import Process +from pywps.app.Common import Metadata +from pywps.response.execute import ExecuteResponse +from .process_defaults import process_defaults + + +class Trans(Process): + def __init__(self): + process_id = 'trans' + defaults = process_defaults(process_id) + + inputs = \ + iog.of_raster(defaults) + \ + iog.raster_input(defaults) + + outputs = iog.output_r() + iog.output_output(is_output_raster=True) + + super().__init__( + self._handler, + identifier=process_id, + version='1.0', + title='transform/wrap a raster', + abstract='Transform and/or wrap raster', + profile='', + metadata=[Metadata('raster')], + inputs=inputs, + outputs=outputs, + store_supported=True, + status_supported=True + ) + + def _handler(self, request, response: ExecuteResponse): + of = str(process_helper.get_request_data(request.inputs, 'of')).lower() + ext = gdalos_util.get_ext_by_of(of) + output_filename = tempfile.mktemp(suffix=ext) + raster_filename = process_helper.get_request_data(request.inputs, 'r') + if of == 'xyz': + gdal2xyz(raster_filename, output_filename, skip_nodata=True) + else: + gdalos_trans(raster_filename, of=of, out_filename=output_filename) + + response.outputs['r'].data = raster_filename + response.outputs['output'].output_format = FORMATS.TEXT + response.outputs['output'].file = output_filename + + return response diff --git a/processes/viewshed.py b/processes/viewshed.py new file mode 100644 index 0000000..57c0993 --- /dev/null +++ b/processes/viewshed.py @@ -0,0 +1,134 @@ +import tempfile + +import processes.io_generator as iog +from backend.formats import czml_format +from gdalos.gdalos_color import ColorPalette +from gdalos.gdalos_main import GeoRectangle, gdalos_util +from gdalos.viewshed.viewshed_calc import viewshed_calc, CalcOperation +from gdalos.viewshed.viewshed_params import ViewshedParams +from processes import process_helper +from pywps import FORMATS +from pywps.app import Process +from pywps.app.Common import Metadata +from pywps.response.execute import ExecuteResponse +from .process_defaults import process_defaults + + +class Viewshed(Process): + def __init__(self): + process_id = 'viewshed' + defaults = process_defaults(process_id) + + inputs = \ + iog.io_crs(defaults) + \ + iog.of_raster(defaults) + \ + iog.raster_input(defaults) + \ + iog.raster_ranges(defaults) + \ + iog.observer(defaults, xy=True, z=True, msl=True) + \ + iog.target(defaults, xy=False, z=True, msl=True) + \ + iog.angles(defaults) + \ + iog.viewshed_values(defaults) + \ + iog.slice(defaults) + \ + iog.backend(defaults) + \ + iog.refraction(defaults) + \ + iog.mode(defaults, default="2") + \ + iog.color_palette(defaults) + \ + iog.extent(defaults) + \ + iog.operation(defaults) + \ + iog.resolution_output(defaults) + \ + iog.radio(defaults) + \ + iog.fake_raster(defaults) + + outputs = iog.output_r() + iog.output_output(is_output_raster=True) + + super().__init__( + self._handler, + identifier=process_id, + version='1.0', + title='viewshed raster analysis', + abstract='runs viewshed or radio analysis', + profile='', + metadata=[Metadata('raster')], + inputs=inputs, + outputs=outputs, + store_supported=True, + status_supported=True + ) + + def _handler(self, request, response: ExecuteResponse): + of = str(process_helper.get_request_data(request.inputs, 'of')).lower() + if of == 'tif': + of = 'gtiff' + ext = gdalos_util.get_ext_by_of(of) + is_czml = ext == '.czml' + + extent = process_helper.get_request_data(request.inputs, 'extent') + if extent is not None: + # I'm not sure why the extent is in format miny, minx, maxy, maxx + extent = [float(x) for x in extent] + extent = GeoRectangle.from_min_max(extent[1], extent[3], extent[0], extent[2]) + else: + extent = request.inputs['extent_c'][0].data + + cutline = process_helper.get_request_data(request.inputs, 'cutline', True) + operation = process_helper.get_request_data(request.inputs, 'o') + if not operation: + operation = None + else: + try: + i = int(operation) + if i == 0: + operation = CalcOperation.viewshed + elif i == 1: + operation = CalcOperation.count + elif i == 2: + operation = CalcOperation.unique + operation = CalcOperation(i) + except ValueError: + try: + operation = CalcOperation[operation] + except ValueError: + raise Exception('unknown operation requested {}'.format(operation)) + + color_palette = process_helper.get_request_data(request.inputs, 'color_palette', True) + if color_palette is None: + if is_czml: + raise Exception('color_palette is required for czml output') + else: + color_palette = ColorPalette.from_string_list(color_palette) + discrete_mode = process_helper.get_request_data(request.inputs, 'discrete_mode') + + output_filename = tempfile.mktemp(suffix=ext) + + co = None + files = [] + if 'fr' in request.inputs: + for fr in request.inputs['fr']: + fr_filename, ds = process_helper.open_ds_from_wps_input(fr) + if operation: + files.append(ds) + else: + output_filename = fr_filename + bi = vp_arrays_dict = in_coords_srs = out_crs = color_palette = backend = raster_filename = ovr_idx = None + + else: + in_coords_srs, out_crs = iog.get_io_crs(request.inputs) + raster_filename, bi, ovr_idx, co = iog.get_input_raster(request.inputs) + backend, vp_arrays_dict = iog.get_vp(request.inputs, ViewshedParams) + + vp_slice = process_helper.get_request_data(request.inputs, 'vps') + + input_file = iog.get_input_file(raster_filename, use_data_selector=True) + + viewshed_calc(input_filename=input_file, ovr_idx=ovr_idx, bi=bi, backend=backend, + output_filename=output_filename, co=co, of=of, + vp_array=vp_arrays_dict, extent=extent, cutline=cutline, operation=operation, + in_coords_srs=in_coords_srs, out_crs=out_crs, + color_palette=color_palette, discrete_mode=discrete_mode, + files=files, vp_slice=vp_slice) + + response.outputs['r'].data = raster_filename + response.outputs['output'].output_format = czml_format if is_czml else FORMATS.GEOTIFF + response.outputs['output'].file = output_filename + + return response diff --git a/processes/xyz.py b/processes/xyz.py new file mode 100644 index 0000000..2bf7bad --- /dev/null +++ b/processes/xyz.py @@ -0,0 +1,61 @@ +import tempfile + +import processes.io_generator as iog +from gdalos.gdalos_main import gdalos_trans +from gdalos.gdalos_main import gdalos_util +from gdalos.gdal2xyz import gdal2xyz +from processes import process_helper +from pywps import FORMATS +from pywps.app import Process +from pywps.app.Common import Metadata +from pywps.response.execute import ExecuteResponse +from .process_defaults import process_defaults + + +class XYZ(Process): + def __init__(self): + process_id = 'xyz' + defaults = process_defaults(process_id) + + inputs = \ + iog.raster_input(defaults) + \ + iog.skip_src_dst_nodata(defaults) + + outputs = iog.output_r() + \ + iog.output_value(['x', 'y', 'z', 'nodata']) + + super().__init__( + self._handler, + identifier=process_id, + version='1.0', + title='raster to xyz', + abstract='returns xy coordinates and band values from a given raster', + profile='', + metadata=[Metadata('raster')], + inputs=inputs, + outputs=outputs, + store_supported=True, + status_supported=True + ) + + def _handler(self, request, response: ExecuteResponse): + raster_filename = process_helper.get_request_data(request.inputs, 'r') + skip_nodata = process_helper.get_request_data(request.inputs, 'skip_nodata') + src_nodata = process_helper.get_request_data(request.inputs, 'src_nodata') + dst_nodata = process_helper.get_request_data(request.inputs, 'dst_nodata') + x, y, z, nodata = gdal2xyz(raster_filename, None, return_np_arrays=True, + skip_nodata=skip_nodata, src_nodata=src_nodata, dst_nodata=dst_nodata) + + response.outputs['r'].data = raster_filename + + response.outputs['nodata'].data = nodata + + response.outputs['x'].output_format = FORMATS.JSON + response.outputs['y'].output_format = FORMATS.JSON + response.outputs['z'].output_format = FORMATS.JSON + + response.outputs['x'].data = x + response.outputs['y'].data = y + response.outputs['z'].data = z + + return response diff --git a/requirements-apache.txt b/requirements-apache.txt new file mode 100644 index 0000000..871fe2b --- /dev/null +++ b/requirements-apache.txt @@ -0,0 +1 @@ +mod_wsgi \ No newline at end of file diff --git a/requirements-iis.txt b/requirements-iis.txt new file mode 100644 index 0000000..e645b58 --- /dev/null +++ b/requirements-iis.txt @@ -0,0 +1 @@ +wfastcgi \ No newline at end of file diff --git a/requirements-opt.txt b/requirements-opt.txt new file mode 100644 index 0000000..9e50af6 --- /dev/null +++ b/requirements-opt.txt @@ -0,0 +1 @@ +talosgis>=2.7.6.3 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e427a56..5a2306e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,10 @@ Shapely Werkzeug SQLAlchemy psutil -pywps +pyyaml +numpy!=1.19.4 +pywps-json>=4.2.9.6 +czml3>=0.5.4 +gdalos>=0.50.2 +gdal-utils>=3.3.0.5 +gdal>=3.0.0 \ No newline at end of file diff --git a/set_project_root_dir.py b/set_project_root_dir.py new file mode 100644 index 0000000..04c9a37 --- /dev/null +++ b/set_project_root_dir.py @@ -0,0 +1,9 @@ +import os + + +def set_project_root_dir(new_root=None): + if not new_root: + new_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + os.chdir(new_root) + print('root path: {}:'.format(os.getcwd())) + diff --git a/set_root.py b/set_root.py new file mode 100644 index 0000000..9c24b5f --- /dev/null +++ b/set_root.py @@ -0,0 +1,8 @@ +import sys +import os + + +def set_root(): + project_root = os.path.dirname(os.path.abspath(__file__)) + sys.path.insert(0, project_root) + os.chdir(project_root) diff --git a/static/data/color_files/gradient/dem-percentages.txt b/static/data/color_files/gradient/dem-percentages.txt new file mode 100644 index 0000000..950f797 --- /dev/null +++ b/static/data/color_files/gradient/dem-percentages.txt @@ -0,0 +1,7 @@ +100% 255 255 255 +90% 235 220 175 +70% 190 185 135 +50% 240 250 150 +30% 50 180 50 +10% 200 230 255 +nv 0 0 0 0 \ No newline at end of file diff --git a/static/data/color_files/gradient/dtm-fixed.txt b/static/data/color_files/gradient/dtm-fixed.txt new file mode 100644 index 0000000..24ccfea --- /dev/null +++ b/static/data/color_files/gradient/dtm-fixed.txt @@ -0,0 +1,7 @@ +500 255 255 255 +450 235 220 175 +300 190 185 135 +150 240 250 150 +76 50 180 50 +75 200 230 255 +nv 0 0 0 0 \ No newline at end of file diff --git a/static/data/color_files/gradient/percentages.txt b/static/data/color_files/gradient/percentages.txt new file mode 100644 index 0000000..b5f8fc7 --- /dev/null +++ b/static/data/color_files/gradient/percentages.txt @@ -0,0 +1,8 @@ +0.0% 0 0 127 204 +16.66% 0 0 255 204 +33.33% 0 255 255 204 +50.00% 0 255 0 204 +66.66% 255 255 0 204 +83.33% 255 0 0 204 +100% 255 0 255 204 +nv 0 0 0 0 diff --git a/static/data/color_files/gradient/percentages1.txt b/static/data/color_files/gradient/percentages1.txt new file mode 100644 index 0000000..b71f367 --- /dev/null +++ b/static/data/color_files/gradient/percentages1.txt @@ -0,0 +1,4 @@ +0% 255 0 0 +50% 0 255 0 +100% 0 0 255 +nv 0 0 0 0 \ No newline at end of file diff --git a/static/data/color_files/viewshed/count.txt b/static/data/color_files/viewshed/count.txt new file mode 100644 index 0000000..262ea72 --- /dev/null +++ b/static/data/color_files/viewshed/count.txt @@ -0,0 +1,6 @@ +0 0 0 0 +1 0 0 143 +2 0 143 255 +3 143 255 127 +4 255 255 0 +5 255 0 0 \ No newline at end of file diff --git a/static/data/color_files/viewshed/count_z.txt b/static/data/color_files/viewshed/count_z.txt new file mode 100644 index 0000000..262ea72 --- /dev/null +++ b/static/data/color_files/viewshed/count_z.txt @@ -0,0 +1,6 @@ +0 0 0 0 +1 0 0 143 +2 0 143 255 +3 143 255 127 +4 255 255 0 +5 255 0 0 \ No newline at end of file diff --git a/static/data/color_files/viewshed/unique.txt b/static/data/color_files/viewshed/unique.txt new file mode 100644 index 0000000..4bbb52d --- /dev/null +++ b/static/data/color_files/viewshed/unique.txt @@ -0,0 +1,7 @@ +0 0 0 143 +1 0 143 255 +2 143 255 127 +3 255 255 0 +4 255 0 0 +254 0 0 0 255 +255 0 0 0 0 \ No newline at end of file diff --git a/static/data/color_files/viewshed/viewshed.txt b/static/data/color_files/viewshed/viewshed.txt new file mode 100644 index 0000000..8eb1393 --- /dev/null +++ b/static/data/color_files/viewshed/viewshed.txt @@ -0,0 +1,6 @@ +0 255 255 255 0 +1 128 128 128 +2 255 0 0 +3 128 0 0 +4 0 128 0 +5 0 255 0 \ No newline at end of file diff --git a/static/data/maps/srtm1_w84u36.tif b/static/data/maps/srtm1_w84u36.tif new file mode 100644 index 0000000..2a00fb9 Binary files /dev/null and b/static/data/maps/srtm1_w84u36.tif differ diff --git a/static/data/maps/srtm1_x35_y32.tif b/static/data/maps/srtm1_x35_y32.tif new file mode 100644 index 0000000..f686a77 Binary files /dev/null and b/static/data/maps/srtm1_x35_y32.tif differ diff --git a/static/data/point.gml b/static/data/shp/point.gml similarity index 100% rename from static/data/point.gml rename to static/data/shp/point.gml diff --git a/static/data/shp/point_buffer.gml b/static/data/shp/point_buffer.gml new file mode 100644 index 0000000..44d219f --- /dev/null +++ b/static/data/shp/point_buffer.gml @@ -0,0 +1,19 @@ + + + + + -0.9514645979959721-0.986306232731747 + 1.0485354020040281.013693767268253 + + + + + + 1.04853540200403,0.013693767268253 1.0471649367586,-0.038642188974691 1.0430572973723,-0.0908346959994 1.03622374259917,-0.142740697771978 1.02668300273783,-0.194217923549506 1.0144612282931,-0.245125277834268 0.999591918299182,-0.295323227106694 0.98211582850123,-0.344674182277047 0.962080859646629,-0.393042875807547 0.939541926192396,-0.440296732471293 0.914560805788467,-0.486306232731747 0.887205969949452,-0.530945267746774 0.857552396378976,-0.57409148502422 0.825681363460999,-0.615626623781584 0.791680227481423,-0.655436839090605 0.755642183190576,-0.693413013918294 0.717666008362887,-0.729451058209141 0.677855793053866,-0.763452194188717 0.636320654296502,-0.795323227106694 0.593174437019056,-0.824976800677171 0.548535402004029,-0.852331636516185 0.502525901743576,-0.877312756920114 0.455272045079829,-0.899851690374347 0.40690335154933,-0.919886659228948 0.357552396378977,-0.9373627490269 0.30735444710655,-0.952232059020815 0.256447092821789,-0.964453833465552 0.204969867044261,-0.973994573326884 0.153063865271684,-0.98082812810002 0.100871358246974,-0.984935767486321 0.048535402004031,-0.986306232731747 -0.003800554238913,-0.984935767486321 -0.055993061263623,-0.980828128100021 -0.1078990630362,-0.973994573326885 -0.159376288813728,-0.964453833465553 -0.21028364309849,-0.952232059020816 -0.260481592370916,-0.937362749026902 -0.309832547541269,-0.91988665922895 -0.358201241071769,-0.899851690374349 -0.405455097735516,-0.877312756920117 -0.451464597995969,-0.852331636516187 -0.496103633010996,-0.824976800677173 -0.539249850288442,-0.795323227106697 -0.580784989045806,-0.763452194188721 -0.620595204354827,-0.729451058209144 -0.658571379182516,-0.693413013918298 -0.694609423473363,-0.655436839090609 -0.72861055945294,-0.615626623781588 -0.760481592370917,-0.574091485024224 -0.790135165941393,-0.530945267746778 -0.817490001780408,-0.486306232731752 -0.842471122184337,-0.440296732471299 -0.865010055638571,-0.393042875807552 -0.885045024493172,-0.344674182277053 -0.902521114291124,-0.2953232271067 -0.917390424285039,-0.245125277834274 -0.929612198729776,-0.194217923549512 -0.939152938591109,-0.142740697771984 -0.945986493364245,-0.090834695999407 -0.950094132750546,-0.038642188974697 -0.951464597995972,0.013693767268246 -0.950094132750546,0.06602972351119 -0.945986493364246,0.118222230535899 -0.939152938591111,0.170128232308477 -0.929612198729779,0.221605458086005 -0.917390424285042,0.272512812370766 -0.902521114291128,0.322710761643193 -0.885045024493177,0.372061716813546 -0.865010055638576,0.420430410344046 -0.842471122184344,0.467684267007793 -0.817490001780415,0.513693767268246 -0.790135165941401,0.558332802283273 -0.760481592370924,0.601479019560719 -0.728610559452948,0.643014158318084 -0.694609423473372,0.682824373627105 -0.658571379182526,0.720800548454794 -0.620595204354837,0.756838592745641 -0.580784989045817,0.790839728725218 -0.539249850288453,0.822710761643195 -0.496103633011006,0.852364335213672 -0.451464597995979,0.879719171052687 -0.405455097735526,0.904700291456617 -0.358201241071779,0.927239224910851 -0.309832547541279,0.947274193765452 -0.260481592370926,0.964750283563404 -0.2102836430985,0.979619593557319 -0.159376288813738,0.991841368002057 -0.107899063036209,1.00138210786339 -0.055993061263632,1.00821566263653 -0.003800554238922,1.01232330202283 0.048535402004022,1.01369376726825 0.100871358246967,1.01232330202283 0.153063865271677,1.00821566263653 0.204969867044254,1.00138210786339 0.256447092821783,0.99184136800206 0.307354447106545,0.979619593557322 0.357552396378972,0.964750283563408 0.406903351549325,0.947274193765456 0.455272045079825,0.927239224910855 0.502525901743572,0.904700291456622 0.548535402004026,0.879719171052693 0.593174437019053,0.852364335213678 0.636320654296499,0.822710761643202 0.677855793053864,0.790839728725225 0.717666008362885,0.756838592745648 0.755642183190575,0.720800548454801 0.791680227481422,0.682824373627112 0.825681363460999,0.643014158318091 0.857552396378975,0.601479019560726 0.887205969949452,0.55833280228328 0.914560805788467,0.513693767268253 0.939541926192396,0.467684267007799 0.962080859646629,0.420430410344052 0.98211582850123,0.372061716813552 0.999591918299182,0.322710761643199 1.0144612282931,0.272512812370772 1.02668300273783,0.22160545808601 1.03622374259917,0.170128232308481 1.0430572973723,0.118222230535904 1.0471649367586,0.066029723511194 1.04853540200403,0.013693767268253 + + + diff --git a/static/data/shp/poly.cpg b/static/data/shp/poly.cpg new file mode 100644 index 0000000..3ad133c --- /dev/null +++ b/static/data/shp/poly.cpg @@ -0,0 +1 @@ +UTF-8 \ No newline at end of file diff --git a/static/data/shp/poly.dbf b/static/data/shp/poly.dbf new file mode 100644 index 0000000..53d5d1b Binary files /dev/null and b/static/data/shp/poly.dbf differ diff --git a/static/data/shp/poly.gml b/static/data/shp/poly.gml new file mode 100644 index 0000000..68144ad --- /dev/null +++ b/static/data/shp/poly.gml @@ -0,0 +1,26 @@ + + + + + 35.1571186440677932.52847457627119 + 35.4513559322033832.78237288135593 + + + + + + 35.1571186440678,32.6708474576271 35.2864406779661,32.7823728813559 35.4513559322034,32.6471186440678 35.3338983050847,32.6115254237288 35.1571186440678,32.6708474576271 + + + + + + 35.1666101694915,32.5913559322034 35.2733898305085,32.606779661017 35.2710169491525,32.536779661017 35.185593220339,32.5284745762712 35.1666101694915,32.5913559322034 + + + + diff --git a/static/data/shp/poly.json b/static/data/shp/poly.json new file mode 100644 index 0000000..46baa71 --- /dev/null +++ b/static/data/shp/poly.json @@ -0,0 +1,9 @@ +{ +"type": "FeatureCollection", +"name": "poly", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, +"features": [ +{ "type": "Feature", "properties": { "id": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 35.157118644067786, 32.670847457627119 ], [ 35.286440677966091, 32.782372881355933 ], [ 35.451355932203377, 32.647118644067803 ], [ 35.33389830508473, 32.611525423728814 ], [ 35.157118644067786, 32.670847457627119 ] ] ] } }, +{ "type": "Feature", "properties": { "id": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 35.166610169491513, 32.591355932203392 ], [ 35.273389830508464, 32.606779661016951 ], [ 35.271016949152525, 32.536779661016951 ], [ 35.185593220338973, 32.528474576271186 ], [ 35.166610169491513, 32.591355932203392 ] ] ] } } +] +} diff --git a/static/data/shp/poly.prj b/static/data/shp/poly.prj new file mode 100644 index 0000000..f45cbad --- /dev/null +++ b/static/data/shp/poly.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]] \ No newline at end of file diff --git a/static/data/shp/poly.qpj b/static/data/shp/poly.qpj new file mode 100644 index 0000000..5fbc831 --- /dev/null +++ b/static/data/shp/poly.qpj @@ -0,0 +1 @@ +GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] diff --git a/static/data/shp/poly.shp b/static/data/shp/poly.shp new file mode 100644 index 0000000..8201415 Binary files /dev/null and b/static/data/shp/poly.shp differ diff --git a/static/data/shp/poly.shx b/static/data/shp/poly.shx new file mode 100644 index 0000000..abe2cb7 Binary files /dev/null and b/static/data/shp/poly.shx differ diff --git a/static/data/shp/poly.xsd b/static/data/shp/poly.xsd new file mode 100644 index 0000000..519e721 --- /dev/null +++ b/static/data/shp/poly.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/railroads.gml b/static/data/shp/railroads.gml similarity index 100% rename from data/railroads.gml rename to static/data/shp/railroads.gml diff --git a/static/postman/talos_wps.postman_collection.json b/static/postman/talos_wps.postman_collection.json new file mode 100644 index 0000000..136d172 --- /dev/null +++ b/static/postman/talos_wps.postman_collection.json @@ -0,0 +1,400 @@ +{ + "info": { + "_postman_id": "52447cf7-4a40-4b97-b73d-9c7b84dea3b2", + "name": "talos_wps", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "viewshed", + "item": [ + { + "name": "viewshed single", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n\n\tviewshed\n\n \n \n output\n \n \n\n\t\n\n\t\t\n of\n \n czml\n \n\t\t\n\n \n ox\n \n 35.11\n \n \n \n oy\n \n 32.06\n \n \n\n \n oz\n \n 10\n \n \n\n \n tz\n \n 10\n \n \n\n \n max_r\n \n 2000\n \n \n\n\n\t\t\n color_palette\n \n\t\t\n\n\t\n\n", + "options": { + "raw": { + "language": "xml" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + }, + { + "name": "viewshed calc target z", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n\n\tviewshed\n\n \n \n output\n \n \n\n\t\n\n\t\t\n of\n \n czml\n \n\t\t\n\n \n ox\n \n 35.11\n \n \n \n oy\n \n 32.06\n \n \n\n \n oz\n \n 10\n \n \n\n \n\n \n max_r\n \n 2000\n \n \n\n\n\t\t\n color_palette\n \n\t\t\n\n \n discrete_mode\n \n up\n \n \n\n\t\n\n", + "options": { + "raw": { + "language": "xml" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + }, + { + "name": "viewshed single ap", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n\n\tviewshed\n\n \n \n output\n \n \n\n\t\n\n\t\t\n of\n \n czml\n \n\t\t\n\n \n ox\n \n 35.11\n \n \n \n oy\n \n 32.06\n \n \n\n \n oz\n \n 10\n \n \n\n \n tz\n \n 10\n \n \n\n \n max_r\n \n 2000\n \n \n\n \n h_aperture\n \n 30\n \n \n\n \n azimuth\n \n 20\n \n \n\n\t\t\n color_palette\n \n\t\t\n\n\t\n\n", + "options": { + "raw": { + "language": "xml" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + }, + { + "name": "viewshed count ap", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n\n\tviewshed\n\n \n \n output\n \n \n\n\t\n\n\t\t\n of\n \n czml\n \n\t\t\n\n \n ox\n \n 35.11\n \n \n \n oy\n \n 32.06\n \n \n\n \n ox\n \n 35.111\n \n \n \n oy\n \n 32.061\n \n \n\n\n \n oz\n \n 10\n \n \n\n \n tz\n \n 10\n \n \n\n \n max_r\n \n 2000\n \n \n\n \n o\n \n count\n \n \n\n\t\t\n color_palette\n \n\t\t\n\t\t\n \n h_aperture\n \n 30\n \n \n\n \n azimuth\n \n 20\n \n \t\t\n\n\t\n\n", + "options": { + "raw": { + "language": "xml" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + }, + { + "name": "viewshed count_z", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n\n\tviewshed\n\n \n \n output\n \n \n\n\t\n\n\t\t\n of\n \n czml\n \n\t\t\n\n \n ox\n \n 35.11\n \n \n \n oy\n \n 32.06\n \n \n\n \n ox\n \n 35.111\n \n \n \n oy\n \n 32.061\n \n \n\n\n \n oz\n \n 10\n \n \n\n \n tz\n \n 10\n \n \n\n \n max_r\n \n 2000\n \n \n\n\n \n o\n \n count_z\n \n \n\n\t\t\n color_palette\n \n\t\t\n\n\t\n\n", + "options": { + "raw": { + "language": "xml" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + }, + { + "name": "viewshed max", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n\n\tviewshed\n\n \n \n output\n \n \n\n\t\n\n\t\t\n of\n \n czml\n \n\t\t\n\n \n ox\n \n 35.11\n \n \n \n oy\n \n 32.06\n \n \n\n \n ox\n \n 35.111\n \n \n \n oy\n \n 32.061\n \n \n\n\n \n oz\n \n 10\n \n \n\n \n tz\n \n 10\n \n \n\n \n max_r\n \n 2000\n \n \n\n\n \n o\n \n max\n \n \n\n\t\t\n color_palette\n \n\t\t\n\n\t\n\n", + "options": { + "raw": { + "language": "xml" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + }, + { + "name": "viewshed unique", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n\n\tviewshed\n\n \n \n output\n \n \n\n\t\n\n\t\t\n of\n \n czml\n \n\t\t\n\n \n ox\n \n 35.11\n \n \n \n oy\n \n 32.06\n \n \n\n \n ox\n \n 35.111\n \n \n \n oy\n \n 32.061\n \n \n\n\n \n oz\n \n 10\n \n \n\n \n tz\n \n 10\n \n \n\n \n max_r\n \n 2000\n \n \n\n \n o\n \n unique\n \n \n\n\t\t\n color_palette\n \n\t\t\n\t\t\n \n h_aperture\n \n 180\n \n \n \n \n h_aperture\n \n 90\n \n \n \n \n \n h_aperture\n \n 60\n \n \n \n \n \n azimuth\n \n 20\n \n \n\t\t\n \n azimuth\n \n 60\n \n \n\t\t\t\t\n\n\t\n\n", + "options": { + "raw": { + "language": "xml" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + }, + { + "name": "viewshed count cutline", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n\n\tviewshed\n\n \n \n output\n \n \n\n\t\n\n\t\t\n of\n \n czml\n \n\t\t\n\n \n ox\n \n 35.11\n \n \n \n oy\n \n 32.06\n \n \n\n \n oz\n \n 10\n \n \n\n \n tz\n \n 10\n \n \n\n \n max_r\n \n 2000\n \n \n \n \n o\n \n count\n \n \n\n \n h_aperture\n \n 180\n \n \n \n \n h_aperture\n \n 90\n \n \n \n \n \n h_aperture\n \n 60\n \n \n \n \n \n azimuth\n \n 20\n \n \n\t\t\n \n azimuth\n \n 60\n \n \n\n\t\t\n color_palette\n \n\t\t\n\n \n cutline\n \n \n\n \n \n \n 35.0994255153583632.04549480546075\n 35.1342894334470932.08511289419796\n \n \n\n \n \n 35.0994255153584,32.0752600477816 35.1185110989761,32.085112894198 35.1342894334471,32.0604463276451 35.1003901296928,32.0454948054607 35.0994255153584,32.0752600477816\n \n \n \n\n\n \n \n\t\t\n\n\t\n\n", + "options": { + "raw": { + "language": "xml" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + }, + { + "name": "viewshed single cutline", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n\n\tviewshed\n\n \n \n output\n \n \n\n\t\n\n\t\t\n of\n \n czml\n \n\t\t\n\n \n ox\n \n 35.11\n \n \n \n oy\n \n 32.06\n \n \n\n \n oz\n \n 10\n \n \n\n \n tz\n \n 10\n \n \n\n \n max_r\n \n 2000\n \n \n\n\n\t\t\n color_palette\n \n\t\t\n\n \n cutline\n \n \n\n \n \n \n 35.0994255153583632.04549480546075\n 35.1342894334470932.08511289419796\n \n \n\n \n \n 35.0994255153584,32.0752600477816 35.1185110989761,32.085112894198 35.1342894334471,32.0604463276451 35.1003901296928,32.0454948054607 35.0994255153584,32.0752600477816\n \n \n \n\n\n \n \n\t\t\n\n\t\n\n", + "options": { + "raw": { + "language": "xml" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "hello", + "item": [ + { + "name": "hello world get", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{talos_wps}}?service=wps&version=1.0.0&request=execute&Identifier=say_hello&storeExecuteResponse=true&DataInputs=name=Idan&RawDataOutput=output", + "host": [ + "{{talos_wps}}" + ], + "query": [ + { + "key": "service", + "value": "wps" + }, + { + "key": "version", + "value": "1.0.0" + }, + { + "key": "request", + "value": "execute" + }, + { + "key": "Identifier", + "value": "say_hello" + }, + { + "key": "storeExecuteResponse", + "value": "true" + }, + { + "key": "DataInputs", + "value": "name=Idan" + }, + { + "key": "RawDataOutput", + "value": "output" + } + ] + } + }, + "response": [] + }, + { + "name": "hello world post", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n\n\tsay_hello\n\n \n \n output\n \n \n\n \n \n name\n \n Idan\n \n \n\t\n \n\n", + "options": { + "raw": { + "language": "xml" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "color_crop", + "item": [ + { + "name": "color_crop_czml", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n\n\tcrop_color\n\n \n \n czml\n \n \n\n \n\n\t\t\n output_tif\n \n False\n \n\t\t\n\n \n output_czml\n \n True\n \n\t\t\n\n\n\n\n\n\n\n\n\n\n\n\t\t\n extent\n \n \n 35.2 32.6\n 35.3 32.7\n\n\n \n \n\t\t\n\n \n cutline\n \n \n\n \n \n \n 35.1571186440677932.52847457627119\n 35.4513559322033832.78237288135593\n \n \n\n \n \n 35.1571186440678,32.6708474576271 35.2864406779661,32.7823728813559 35.4513559322034,32.6471186440678 35.3338983050847,32.6115254237288 35.1571186440678,32.6708474576271\n \n \n \n \n \n 35.1666101694915,32.5913559322034 35.2733898305085,32.606779661017 35.2710169491525,32.536779661017 35.185593220339,32.5284745762712 35.1666101694915,32.5913559322034\n \n \n \n \n\n \n \n\t\t\n\n\t\t\n color_palette\n \n \n 100% 255 255 255 \n 75% 235 220 175 \n 20% 190 185 135 \n 0% 240 250 150 \n nv 0 0 0 0\n \n\n\n\n\n\n\n\n\n\n \n\t\t\n\n\t\n\n", + "options": { + "raw": { + "language": "xml" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "radio", + "item": [ + { + "name": "radio viewshed", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\t\"identifier\": \"viewshed\",\n\t\"outputs\": \"output\",\n\t\"inputs\": {\n\t\t\"backend\": \"radio\",\n\t\t\"of\": \"tif\",\n \"color_palette\": {\n\t\t\t\"type\": \"reference\",\n \"href\": \"file:./static/data/color_files/viewshed/count_z.txt\"\n },\n\t\t\"max_r\": \"2000\",\n\t\t\"ox\": \"35.11\",\n\t\t\"oy\": \"32.06\",\n\t\t\"oz\": \"10.0\",\n\t\t\"tz\": \"10.0\",\n\t\t\"omsl\": \"False\",\n\t\t\"tmsl\": \"False\"\n\t}\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + }, + { + "name": "radio los", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\t\"identifier\": \"los\",\n\t\"outputs\": \"output\",\n\t\"inputs\": {\n\t\t\"ox\": [35.11, 35.12, 35.13, 35.14],\n\t\t\"oy\": [32.0, 32.07],\n\t\t\"oz\": [10.0],\n\t\t\"tx\": [35.18, 35.19],\n\t\t\"ty\": [32.08, 32.09],\n\t\t\"tz\": [11.0],\n \"xy_fill\": \"zip_cycle\",\n \"ot_fill\": \"product\",\n\t\t\"omsl\": \"False\",\n\t\t\"tmsl\": \"False\",\n \"mode\": [\"PathLoss\", \"FreeSpaceLoss\"],\n \"mock\": false\n\t}\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "trans", + "item": [ + { + "name": "trans 1", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\t\"identifier\": \"trans\",\n\t\"outputs\": \"output\",\n\t\"inputs\": {\n \"r\": \"d:\\\\Maps\\\\samples\\\\rgb.tif\", \n \"of\": \"xyz\"\n\t}\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{talos_wps}}", + "host": [ + "{{talos_wps}}" + ] + } + }, + "response": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/static/postman/talos_wps_env.postman_environment.json b/static/postman/talos_wps_env.postman_environment.json new file mode 100644 index 0000000..f225852 --- /dev/null +++ b/static/postman/talos_wps_env.postman_environment.json @@ -0,0 +1,14 @@ +{ + "id": "400bec27-c8e4-4470-8346-4723cb17c130", + "name": "talos_wps_env", + "values": [ + { + "key": "talos_wps", + "value": "http://localhost:5000/wps", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2020-05-31T11:51:41.429Z", + "_postman_exported_using": "Postman/7.25.0" +} \ No newline at end of file diff --git a/static/requests/_examples.sh b/static/requests/_examples.sh new file mode 100644 index 0000000..6a142a8 --- /dev/null +++ b/static/requests/_examples.sh @@ -0,0 +1,48 @@ +shopt -s expand_aliases +server=http://localhost:5000/wps + +## GET +# GetCapabilities +curl "$server?service=wps&request=getcapabilities" + +# describeprocess +curl "$server?service=wps&version=1.0.0&request=describeprocess&identifier=viewshed" + +# Get Info +curl "$server?service=wps&version=1.0.0&request=execute&Identifier=info&storeExecuteResponse=trueRawDataOutput=output&RawDataOutput=output" + +# say hello +curl "$server?service=wps&version=1.0.0&request=execute&Identifier=say_hello&storeExecuteResponse=true&DataInputs=name=Idan&RawDataOutput=output" + +#ls +curl "$server?service=wps&version=1.0.0&request=execute&Identifier=ls&storeExecuteResponse=true&DataInputs=dir=./static/data/maps&DataInputs=pattern=*.tif&RawDataOutput=output" + +# ras_val +curl "$server?service=wps&version=1.0.0&request=execute&Identifier=ras_val&storeExecuteResponse=true&DataInputs=x=35.1;y=32.1&RawDataOutput=output" +curl "$server?service=wps&version=1.0.0&request=execute&Identifier=ras_val&storeExecuteResponse=true&DataInputs=x=35.1;y=32.1;r=@xlink:href=file:./static/data/maps/srtm1_x35_y32.tif&RawDataOutput=output" +curl -H "Content-type: xml" -X POST -d@./data/requests/ras_val.xml $server + +## color crop +curl -H "Content-type: xml" -X POST -d@./data/requests/crop_color_czml.xml $server # -o ./outputs/crop_color_czml.czml && cat ./outputs/crop_sample_czml.czml +curl -H "Content-type: xml" -X POST -d@./data/requests/crop_color_o.xml $server -o outputs/crop_color_o.tif + +# calc +curl -H "Content-type: xml" -X POST -d@./data/requests/calc_comb.xml $server -o outputs/calc_comb.tif + +# viewshed with sampled data +curl "$server?service=wps&version=1.0.0&request=execute&Identifier=viewshed&storeExecuteResponse=true&DataInputs=ox=35.21317;oy=32.03437;oz=100;tz=0;md=1&RawDataOutput=tif" +curl "$server?service=wps&version=1.0.0&request=execute&Identifier=viewshed&storeExecuteResponse=true&DataInputs=r=@xlink:href=file:./static/data/maps/srtm1_x33-62_y23-39.tif;ox=35.21317;oy=32.03437;oz=100;tz=0;md=1" +curl "$server?service=wps&version=1.0.0&request=execute&Identifier=viewshed&storeExecuteResponse=true&DataInputs=r=@xlink:href=file:./static/data/maps/srtm1_x35_y32.tif;ox=35.21317;oy=32.03437;oz=100;tz=0;md=1" # -o ./outputs/viewshed.tif && cat ./outputs/viewshed.tif +curl -H "Content-type: xml" -X POST -d@./data/requests/viewshed.xml $server -o outputs/viewshed.tif +curl -H "Content-type: xml" -X POST -d@./data/requests/viewshed_comb.xml $server -o outputs/viewshed_comb.tif +curl -H "Content-type: xml" -X POST -d@./data/requests/viewshed_comb_czml.xml $server -o outputs/viewshed_comb.czml + + +## clear request cache +rm ./logs/pywps-logs.sqlite3 + +# Sand Castle +#https://sandcastle.cesium.com/?src=CZML%20Polyline.html&label=CZML + +# sleep +curl "%server%?service=wps&version=1.0.0&request=execute&Identifier=sleep&storeExecuteResponse=true&DataInputs=seconds=2&RawDataOutput=output" \ No newline at end of file diff --git a/static/requests/execute_buffer_post.xml b/static/requests/buffer.xml similarity index 100% rename from static/requests/execute_buffer_post.xml rename to static/requests/buffer.xml diff --git a/static/requests/execute_buffer_post_referenceinput.xml b/static/requests/buffer_referenceinput.xml similarity index 95% rename from static/requests/execute_buffer_post_referenceinput.xml rename to static/requests/buffer_referenceinput.xml index 7ae082b..6565d3f 100644 --- a/static/requests/execute_buffer_post_referenceinput.xml +++ b/static/requests/buffer_referenceinput.xml @@ -4,7 +4,7 @@ poly_in - + buffer diff --git a/static/requests/execute_buffer_post_referenceoutput.xml b/static/requests/buffer_referenceoutput.xml similarity index 100% rename from static/requests/execute_buffer_post_referenceoutput.xml rename to static/requests/buffer_referenceoutput.xml diff --git a/static/requests/calc_comb.xml b/static/requests/calc_comb.xml new file mode 100644 index 0000000..c02b699 --- /dev/null +++ b/static/requests/calc_comb.xml @@ -0,0 +1,87 @@ + + + calc + + + + tif + + + + + + + output_tif + + True + + + + + output_czml + + False + + + + + r + + + + r + + + + r + + + + r + + + + r + + + + r + + + + r + + + + r + + + + r + + + + + m + + 2 + + + + + + + + + + + + + + + color_palette + + + + + diff --git a/static/requests/calc_comb_czml.xml b/static/requests/calc_comb_czml.xml new file mode 100644 index 0000000..cac8174 --- /dev/null +++ b/static/requests/calc_comb_czml.xml @@ -0,0 +1,87 @@ + + + calc + + + + czml + + + + + + + output_tif + + False + + + + + output_czml + + True + + + + + r + + + + r + + + + r + + + + r + + + + r + + + + r + + + + r + + + + r + + + + r + + + + + m + + 2 + + + + + + + + + + + + + + + color_palette + + + + + diff --git a/static/requests/crop.xml b/static/requests/crop.xml new file mode 100644 index 0000000..3adbcac --- /dev/null +++ b/static/requests/crop.xml @@ -0,0 +1,105 @@ + + + crop_color + + + + tif + + + + + + + output_tif + + True + + + + + output_czml + + False + + + + + r + + + + + + + + + + + + + + + extent + + + 35.2 32.6 + 35.3 32.7 + + + + + + + + cutline + + + + + + + 35.1571186440677932.52847457627119 + 35.4513559322033832.78237288135593 + + + + + + 35.1571186440678,32.6708474576271 35.2864406779661,32.7823728813559 35.4513559322034,32.6471186440678 35.3338983050847,32.6115254237288 35.1571186440678,32.6708474576271 + + + + + + 35.1666101694915,32.5913559322034 35.2733898305085,32.606779661017 35.2710169491525,32.536779661017 35.185593220339,32.5284745762712 35.1666101694915,32.5913559322034 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/requests/crop_color_czml.xml b/static/requests/crop_color_czml.xml new file mode 100644 index 0000000..cf4c956 --- /dev/null +++ b/static/requests/crop_color_czml.xml @@ -0,0 +1,103 @@ + + + crop_color + + + + czml + + + + + + + + + + + + + + + + + + extent + + + 35.02 32.02 + 35.15 32.15 + + + + + + + + cutline + + + + + + + 35.0297133258258632.00907837837838 + 35.1152193068068432.09218463463463 + + + + + + 35.0297133258259,32.0832172422422 35.0487848223224,32.0918057307307 35.0596467342343,32.0729868368368 35.0359020895896,32.0622512262262 35.0297133258259,32.0832172422422 + + + + + 35.084275487988,32.076396971972 35.0905905530531,32.0921846346346 35.1152193068068,32.0787966966967 35.0905905530531,32.0652824574575 35.084275487988,32.076396971972 + + + + + 35.0669722097097,32.0612408158158 35.0563629004004,32.0426745245245 35.0787182307308,32.0429271271271 35.0669722097097,32.0612408158158 + + + + + 35.0390596221222,32.0396432932933 35.0510582457458,32.0227189189189 35.0845280905906,32.0234767267267 35.1056204079079,32.0396432932933 35.0921061686687,32.0099624874875 35.0497952327328,32.0090783783784 35.0390596221222,32.0396432932933 + + + + + + + + + + + color_palette + + + 100% 255 255 255 + 75% 235 220 175 + 20% 190 185 135 + 0% 240 250 150 + nv 0 0 0 0 + + + + + + + + + + + + + + + diff --git a/static/requests/crop_color_o.xml b/static/requests/crop_color_o.xml new file mode 100644 index 0000000..d8cd660 --- /dev/null +++ b/static/requests/crop_color_o.xml @@ -0,0 +1,56 @@ + + + crop_color + + + czml + + + + + r + + + + output_tif + + False + + + + output_czml + + True + + + + extent + + + 35.1987306960257 32.42406868664126 + 35.515093904780464 32.8458862983143 + + + + + color_palette + + + 100% 255 255 255 + + 90% 235 220 175 + + 70% 190 185 135 + + 50% 240 250 150 + + 30% 50 180 50 + + 10% 200 230 255 + + nv 0 0 0 0 + + + + + \ No newline at end of file diff --git a/static/requests/ras_val.xml b/static/requests/ras_val.xml new file mode 100644 index 0000000..8fdea0b --- /dev/null +++ b/static/requests/ras_val.xml @@ -0,0 +1,31 @@ + + + ras_val + + + + output + + + + + + + x + + 35.1 + + + + + + y + + 32.1 + + + + + + + diff --git a/static/requests/say_hello.xml b/static/requests/say_hello.xml new file mode 100644 index 0000000..6561abd --- /dev/null +++ b/static/requests/say_hello.xml @@ -0,0 +1,20 @@ + + + say_hello + + + + output + + + + + + name + + Idan + + + + + diff --git a/static/requests/sleep.xml b/static/requests/sleep.xml new file mode 100644 index 0000000..a015123 --- /dev/null +++ b/static/requests/sleep.xml @@ -0,0 +1,27 @@ + + + sleep + + + + output + + + + + + delay + + 0.5 + + + + + times + + 2 + + + + + diff --git a/static/requests/viewshed.xml b/static/requests/viewshed.xml new file mode 100644 index 0000000..2de7c03 --- /dev/null +++ b/static/requests/viewshed.xml @@ -0,0 +1,68 @@ + + + viewshed + + + + tif + + + + + + + + + + + + + + in_crs + + + + + + + ox + + 700_000 + + + + + oy + + 3550_000 + + + + + oz + + 10 + + + + + tz + + 10 + + + + + md + + 2000 + + + + + color_palette + + + + + diff --git a/static/requests/viewshed_comb.xml b/static/requests/viewshed_comb.xml new file mode 100644 index 0000000..a178fce --- /dev/null +++ b/static/requests/viewshed_comb.xml @@ -0,0 +1,257 @@ + + + viewshed + + + + output + + + + + + + of + + GTiff + + + + + out_crs + + + + + + + in_crs + + + + + + + + + + + + + + + + + + + + + + + + + + + ox + + 699_000 + + + + ox + + 699_000 + + + + ox + + 699_000 + + + + + ox + + 700_000 + + + + ox + + 700_000 + + + + ox + + 700_000 + + + + + ox + + 701_000 + + + + ox + + 701_000 + + + + ox + + 701_000 + + + + + + oy + + 3549_000 + + + + oy + + 3550_000 + + + + oy + + 3551_000 + + + + + oy + + 3549_000 + + + + oy + + 3550_000 + + + + oy + + 3551_000 + + + + + oy + + 3549_000 + + + + oy + + 3550_000 + + + + oy + + 3551_000 + + + + + oz + + 10 + + + + + tz + + 10 + + + + + md + + 2000 + + + + + + o + + count + + + + + m + + 2 + + + + + color_palette + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/requests/viewshed_comb_4326.xml b/static/requests/viewshed_comb_4326.xml new file mode 100644 index 0000000..3fd354d --- /dev/null +++ b/static/requests/viewshed_comb_4326.xml @@ -0,0 +1,272 @@ + + + viewshed + + + + output + + + + + + + of + + GTiff + + + + + crs + + 0 + + + + ox + + 35 + + + + oy + + 32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cutline + + + + + + + 35.0994255153583632.04549480546075 + 35.1342894334470932.08511289419796 + + + + + + 35.0994255153584,32.0752600477816 35.1185110989761,32.085112894198 35.1342894334471,32.0604463276451 35.1003901296928,32.0454948054607 35.0994255153584,32.0752600477816 + + + + + + + + + + + oz + + 10 + + + + + tz + + 10 + + + + + md + + 2000 + + + + + + o + + count + + + + + m + + 2 + + + + + color_palette + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/requests/viewshed_comb_czml.xml b/static/requests/viewshed_comb_czml.xml new file mode 100644 index 0000000..0c01653 --- /dev/null +++ b/static/requests/viewshed_comb_czml.xml @@ -0,0 +1,118 @@ + + + viewshed + + + + output + + + + + + + of + + czml + + + + + ox + + 35.11 + + + + oy + + 32.06 + + + + + ox + + 35.111 + + + + oy + + 32.061 + + + + + + oz + + 10 + + + + + tz + + 10 + + + + + md + + 2000 + + + + + + o + + count + + + + + m + + 2 + + + + + color_palette + + + + + cutline + + + + + + + 35.0994255153583632.04549480546075 + 35.1342894334470932.08511289419796 + + + + + + 35.0994255153584,32.0752600477816 35.1185110989761,32.085112894198 35.1342894334471,32.0604463276451 35.1003901296928,32.0454948054607 35.0994255153584,32.0752600477816 + + + + + + + + + + + diff --git a/templates/base.html b/templates/base.html index 9e4c21a..ab30a52 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,5 +1,6 @@ + @@ -23,7 +24,7 @@
- + PyWPS Logo - + OSGeo Incubation
{% block major_content %} {% endblock %}