├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── __init__.py ├── band_dialog.py ├── band_dialog.ui ├── drawPoly.py ├── drawRect.py ├── examples ├── create_processgraph.png └── job_list.png ├── help ├── Makefile ├── make.bat └── source │ ├── conf.py │ └── index.rst ├── i18n └── af.ts ├── images ├── deleteFinalBtn.png ├── delete_job.png ├── display_icon.svg ├── download.png ├── edit_icon.png ├── execute_icon.svg ├── icon.png ├── icon_new.png ├── icon_old.png ├── info_icon.png ├── openEO_plugin_header.png ├── pause.png ├── processGraph_icon.png ├── reload_icon.png ├── stop-button.png └── user_manual_icon.png ├── job_adapt_dialog.py ├── job_adapt_dialog.ui ├── job_detail_dialog.py ├── job_detail_dialog.ui ├── login_dialog.py ├── login_dialog.ui ├── metadata.txt ├── models ├── backend.py ├── connect.py ├── graphbuilder.py ├── models.py ├── openeohub.py ├── processgraph.py └── result.py ├── openeo_connector.py ├── openeo_connector_dialog.py ├── openeo_connector_dialog_base.ui ├── pb_tool.cfg ├── plugin_upload.py ├── pylintrc ├── resources.py ├── resources.qrc ├── scripts ├── compile-strings.sh ├── run-env-linux.sh └── update-strings.sh ├── service_create_dialog.py ├── service_create_dialog.ui ├── spatial_dialog.py ├── spatial_dialog.ui ├── temp_dialog.py ├── temp_dialog.ui ├── test ├── __init__.py ├── qgis_interface.py ├── tenbytenraster.asc ├── tenbytenraster.asc.aux.xml ├── tenbytenraster.keywords ├── tenbytenraster.lic ├── tenbytenraster.prj ├── tenbytenraster.qml ├── test_init.py ├── test_openeo_connector_dialog.py ├── test_qgis_environment.py ├── test_resources.py ├── test_translations.py └── utilities.py ├── user_manual_text.txt └── utils └── logging.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #/*************************************************************************** 2 | # OpenEO 3 | # 4 | # Plugin to access openEO compliant backends. 5 | # ------------------- 6 | # begin : 2019-07-18 7 | # git sha : $Format:%H$ 8 | # copyright : (C) 2019 by Bernhard Goesswein 9 | # email : bernhard.goesswein@geo.tuwien.ac.at 10 | # ***************************************************************************/ 11 | # 12 | #/*************************************************************************** 13 | # * * 14 | # * This program is free software; you can redistribute it and/or modify * 15 | # * it under the terms of the GNU General Public License as published by * 16 | # * the Free Software Foundation; either version 2 of the License, or * 17 | # * (at your option) any later version. * 18 | # * * 19 | # ***************************************************************************/ 20 | 21 | ################################################# 22 | # Edit the following to match your sources lists 23 | ################################################# 24 | 25 | 26 | #Add iso code for any locales you want to support here (space separated) 27 | # default is no locales 28 | # LOCALES = af 29 | LOCALES = 30 | 31 | # If locales are enabled, set the name of the lrelease binary on your system. If 32 | # you have trouble compiling the translations, you may have to specify the full path to 33 | # lrelease 34 | #LRELEASE = lrelease 35 | #LRELEASE = lrelease-qt4 36 | 37 | 38 | # translation 39 | SOURCES = \ 40 | __init__.py \ 41 | openeo_connector.py openeo_connector_dialog.py 42 | 43 | PLUGINNAME = openeo_connector 44 | 45 | PY_FILES = \ 46 | __init__.py \ 47 | openeo_connector.py openeo_connector_dialog.py 48 | 49 | UI_FILES = openeo_connector_dialog_base.ui 50 | 51 | EXTRAS = metadata.txt icon.png 52 | 53 | EXTRA_DIRS = 54 | 55 | COMPILED_RESOURCE_FILES = resources.py 56 | 57 | PEP8EXCLUDE=pydev,resources.py,conf.py,third_party,ui 58 | 59 | # QGISDIR points to the location where your plugin should be installed. 60 | # This varies by platform, relative to your HOME directory: 61 | # * Linux: 62 | # .local/share/QGIS/QGIS3/profiles/default/python/plugins/ 63 | # * Mac OS X: 64 | # Library/Application Support/QGIS/QGIS3/profiles/default/python/plugins 65 | # * Windows: 66 | # AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins' 67 | 68 | QGISDIR=/home/bgoesswe/.local/share/QGIS/QGIS3/profiles/default/python/plugins/ 69 | 70 | ################################################# 71 | # Normally you would not need to edit below here 72 | ################################################# 73 | 74 | HELP = help/build/html 75 | 76 | PLUGIN_UPLOAD = $(c)/plugin_upload.py 77 | 78 | RESOURCE_SRC=$(shell grep '^ *@@g;s/.*>//g' | tr '\n' ' ') 79 | 80 | .PHONY: default 81 | default: 82 | @echo While you can use make to build and deploy your plugin, pb_tool 83 | @echo is a much better solution. 84 | @echo A Python script, pb_tool provides platform independent management of 85 | @echo your plugins and runs anywhere. 86 | @echo You can install pb_tool using: pip install pb_tool 87 | @echo See https://g-sherman.github.io/plugin_build_tool/ for info. 88 | 89 | compile: $(COMPILED_RESOURCE_FILES) 90 | 91 | %.py : %.qrc $(RESOURCES_SRC) 92 | pyrcc5 -o $*.py $< 93 | 94 | %.qm : %.ts 95 | $(LRELEASE) $< 96 | 97 | test: compile transcompile 98 | @echo 99 | @echo "----------------------" 100 | @echo "Regression Test Suite" 101 | @echo "----------------------" 102 | 103 | @# Preceding dash means that make will continue in case of errors 104 | @-export PYTHONPATH=`pwd`:$(PYTHONPATH); \ 105 | export QGIS_DEBUG=0; \ 106 | export QGIS_LOG_FILE=/dev/null; \ 107 | nosetests -v --with-id --with-coverage --cover-package=. \ 108 | 3>&1 1>&2 2>&3 3>&- || true 109 | @echo "----------------------" 110 | @echo "If you get a 'no module named qgis.core error, try sourcing" 111 | @echo "the helper script we have provided first then run make test." 112 | @echo "e.g. source run-env-linux.sh ; make test" 113 | @echo "----------------------" 114 | 115 | deploy: compile doc transcompile 116 | @echo 117 | @echo "------------------------------------------" 118 | @echo "Deploying plugin to your .qgis2 directory." 119 | @echo "------------------------------------------" 120 | # The deploy target only works on unix like operating system where 121 | # the Python plugin directory is located at: 122 | # $HOME/$(QGISDIR)/python/plugins 123 | mkdir -p $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 124 | cp -vf $(PY_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 125 | cp -vf $(UI_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 126 | cp -vf $(COMPILED_RESOURCE_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 127 | cp -vf $(EXTRAS) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 128 | cp -vfr i18n $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 129 | cp -vfr $(HELP) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)/help 130 | # Copy extra directories if any 131 | (foreach EXTRA_DIR,(EXTRA_DIRS), cp -R (EXTRA_DIR) (HOME)/(QGISDIR)/python/plugins/(PLUGINNAME)/;) 132 | 133 | 134 | # The dclean target removes compiled python files from plugin directory 135 | # also deletes any .git entry 136 | dclean: 137 | @echo 138 | @echo "-----------------------------------" 139 | @echo "Removing any compiled python files." 140 | @echo "-----------------------------------" 141 | find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname "*.pyc" -delete 142 | find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname ".git" -prune -exec rm -Rf {} \; 143 | 144 | 145 | derase: 146 | @echo 147 | @echo "-------------------------" 148 | @echo "Removing deployed plugin." 149 | @echo "-------------------------" 150 | rm -Rf $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 151 | 152 | zip: deploy dclean 153 | @echo 154 | @echo "---------------------------" 155 | @echo "Creating plugin zip bundle." 156 | @echo "---------------------------" 157 | # The zip target deploys the plugin and creates a zip file with the deployed 158 | # content. You can then upload the zip file on http://plugins.qgis.org 159 | rm -f $(PLUGINNAME).zip 160 | cd $(HOME)/$(QGISDIR)/python/plugins; zip -9r $(CURDIR)/$(PLUGINNAME).zip $(PLUGINNAME) 161 | 162 | package: compile 163 | # Create a zip package of the plugin named $(PLUGINNAME).zip. 164 | # This requires use of git (your plugin development directory must be a 165 | # git repository). 166 | # To use, pass a valid commit or tag as follows: 167 | # make package VERSION=Version_0.3.2 168 | @echo 169 | @echo "------------------------------------" 170 | @echo "Exporting plugin to zip package. " 171 | @echo "------------------------------------" 172 | rm -f $(PLUGINNAME).zip 173 | git archive --prefix=$(PLUGINNAME)/ -o $(PLUGINNAME).zip $(VERSION) 174 | echo "Created package: $(PLUGINNAME).zip" 175 | 176 | upload: zip 177 | @echo 178 | @echo "-------------------------------------" 179 | @echo "Uploading plugin to QGIS Plugin repo." 180 | @echo "-------------------------------------" 181 | $(PLUGIN_UPLOAD) $(PLUGINNAME).zip 182 | 183 | transup: 184 | @echo 185 | @echo "------------------------------------------------" 186 | @echo "Updating translation files with any new strings." 187 | @echo "------------------------------------------------" 188 | @chmod +x scripts/update-strings.sh 189 | @scripts/update-strings.sh $(LOCALES) 190 | 191 | transcompile: 192 | @echo 193 | @echo "----------------------------------------" 194 | @echo "Compiled translation files to .qm files." 195 | @echo "----------------------------------------" 196 | @chmod +x scripts/compile-strings.sh 197 | @scripts/compile-strings.sh $(LRELEASE) $(LOCALES) 198 | 199 | transclean: 200 | @echo 201 | @echo "------------------------------------" 202 | @echo "Removing compiled translation files." 203 | @echo "------------------------------------" 204 | rm -f i18n/*.qm 205 | 206 | clean: 207 | @echo 208 | @echo "------------------------------------" 209 | @echo "Removing uic and rcc generated files" 210 | @echo "------------------------------------" 211 | rm $(COMPILED_UI_FILES) $(COMPILED_RESOURCE_FILES) 212 | 213 | doc: 214 | @echo 215 | @echo "------------------------------------" 216 | @echo "Building documentation using sphinx." 217 | @echo "------------------------------------" 218 | cd help; make html 219 | 220 | pylint: 221 | @echo 222 | @echo "-----------------" 223 | @echo "Pylint violations" 224 | @echo "-----------------" 225 | @pylint --reports=n --rcfile=pylintrc . || true 226 | @echo 227 | @echo "----------------------" 228 | @echo "If you get a 'no module named qgis.core' error, try sourcing" 229 | @echo "the helper script we have provided first then run make pylint." 230 | @echo "e.g. source run-env-linux.sh ; make pylint" 231 | @echo "----------------------" 232 | 233 | 234 | # Run pep8 style checking 235 | #http://pypi.python.org/pypi/pep8 236 | pep8: 237 | @echo 238 | @echo "-----------" 239 | @echo "PEP8 issues" 240 | @echo "-----------" 241 | @pep8 --repeat --ignore=E203,E121,E122,E123,E124,E125,E126,E127,E128 --exclude $(PEP8EXCLUDE) . || true 242 | @echo "-----------" 243 | @echo "Ignored in PEP8 check:" 244 | @echo $(PEP8EXCLUDE) 245 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openeo-qgis-plugin 2 | QGIS Plugin to connect and use openEO compliant backends. 3 | 4 | Compatible with backends compialnt wit openeo API version 0.4.0 and above. Full functionality is only provided for version 1.0.0-rc2. 5 | 6 | Tested with QGIS 3.4.8-Madeira, but works with QGIS >= 3.0.0. 7 | 8 | ## About 9 | 10 | The openEO QGIS Plugin "OpenEO" allows connecting to an openEO backend, managing the user ([batch](https://openeo.org/documentation/1.0/glossary.html#data-processing-modes)) jobs and load the resulting images into a new QGIS Layer. It is also possible to adapt jobs by changing and adding processes. Even though it is generally possible to create own process graphs from scretch it is recommended to use the [openEO web editor](https://editor.openeo.org/). 11 | 12 | ### Features 13 | 14 | * Listing all openeo compliant backends with minimum API version 0.4.0 listet at the [openEO Hub](http://hub.openeo.org/) 15 | * Connecting to an openeo compliant backend (openAPI version above 0.4.0, 1.0.0-rc2 recommended) 16 | * Listing all available collections ("Load Collection" combobox) and processes ("Add Process" combobox) including parameters (see table under the process combobox) of the backend 17 | * Spatial extent selection ("Add Spatial Extent" combobox) by: 18 | * Current extent of the QGIS map canvas 19 | * Drawing rectangle/boundingbox on the QGIS map 20 | * Drawing polygon on the QGIS map(generates minimum bounding box for the whole polygon area) 21 | * Load Extent of a QGIS Layer 22 | * Load Extent from a Shapefile on the filesystem 23 | * Manual input of the spatial extent 24 | * Temporal extent selection via calendar widgets ("Select Star and End Date" button) 25 | * Creating a new job at the backend ("Create Job" button) 26 | * Adapt existing jobs by adapting process arguments and add new processes to the process graph. 27 | * Open the official openeo web editor at the system browser ("Create New Job in Web Editor" button) 28 | * List all jobs of the user at the backend. 29 | * The jobs can be executed and/or the result displayed at the QGIS map. There is also a description displayed at the job table. 30 | * List of openEO services of the user and possibility to show them in QGIS. 31 | 32 | Need some additional features? Leave an issue at this repository! 33 | 34 | ## Install 35 | 36 | There are two options on installing the plugin to your local QGIS Desktop application: 37 | 38 | ### Install via Plugin Manager 39 | 40 | This is the recommended way if you want to get the **most recent stable** version of the plugin. 41 | 42 | 1. Start QGIS Desktop application 43 | 2. Go to "Plugins" and then "Manage and Install Plugins" 44 | 3. Go to "Settings" and make sure that "Show also experimental plugins" is activated 45 | 4. Go to "Not Installed" and search for "OpenEO" 46 | 5. Click on "OpenEO" and click "Install" 47 | 6. The openEO logo should be visible in the toolbar 48 | 49 | ### Install from GitHub repository 50 | 51 | This is the recommended way if you want to get the **most recent** version of the plugin. 52 | 53 | 1. Download this repository as zip file 54 | 2. Start QGIS Desktop application 55 | 3. Go to "Plugins" and then "Manage and Install Plugins" 56 | 4. Click on "Install from ZIP" and choose the downloaded zip file 57 | 5. Press "Install Plugin" 58 | 6. You may have to activate it in the plugin manager (in "Installed") 59 | 7. The openEO logo should be visible in the toolbar 60 | 61 | ## Usage 62 | 63 | First you have to connect to the server with your user credentials in the upper part of the Window. Then all existing jobs are displayed at the "Jobs" tab. There the jobs can be executed and displayed in QGIS. Pressing the "pencil" button on an job entry, a job adaption window will be displayed and the job can be edited and sent as a new job to the backend. 64 | 65 | The following Screenshots show how it looks like: 66 | 67 | create-processgraph 68 | 69 | job-list 70 | 71 | ## Building 72 | 73 | To build the plugin and deploy to your plugin directory you will need the [pb_tool](http://g-sherman.github.io/plugin_build_tool/) CLI tool. 74 | 75 | To compile the plugin run the following command in the root directory of this repository: 76 | 77 | pb_tool compile 78 | 79 | Compiling is needed any time the resources.py file needs to be rebuilt. 80 | 81 | To deploy the application to your QGIS plugins directory run the following command and reload the plugin within QGIS: 82 | 83 | pb_tool deploy 84 | 85 | It's recommended to use the Plugin Reloader plugin within QGIS to easily reload the plugin during development. 86 | 87 | ## Troubleshooting 88 | 89 | #### QGIS cannot find plugin 90 | 91 | Change pb_tool.cfg settings: 92 | 93 | Mac 94 | 95 | plugin_path: /Users/{USER}/Library/Application Support/QGIS/QGIS3/profiles/default/python/plugins/ 96 | 97 | Linux 98 | 99 | plugin_path: /home/{USER}/.local/share/QGIS/QGIS3/profiles/default/python/plugins 100 | 101 | Windows 102 | 103 | plugin_path: C:\Users\{USER}\AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins 104 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | /*************************************************************************** 4 | OpenEO 5 | A QGIS plugin 6 | Plugin to access openEO compliant backends. 7 | Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ 8 | ------------------- 9 | begin : 2019-07-18 10 | copyright : (C) 2019 by Bernhard Goesswein 11 | email : bernhard.goesswein@geo.tuwien.ac.at 12 | git sha : $Format:%H$ 13 | ***************************************************************************/ 14 | 15 | /*************************************************************************** 16 | * * 17 | * This program is free software; you can redistribute it and/or modify * 18 | * it under the terms of the GNU General Public License as published by * 19 | * the Free Software Foundation; either version 2 of the License, or * 20 | * (at your option) any later version. * 21 | * * 22 | ***************************************************************************/ 23 | This script initializes the plugin, making it known to QGIS. 24 | """ 25 | 26 | 27 | # noinspection PyPep8Naming 28 | def classFactory(iface): # pylint: disable=invalid-name 29 | """Load OpenEO class from file OpenEO. 30 | 31 | :param iface: A QGIS interface instance. 32 | :type iface: QgsInterface 33 | """ 34 | # 35 | from .openeo_connector import OpenEO 36 | return OpenEO(iface) 37 | -------------------------------------------------------------------------------- /band_dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | /*************************************************************************** 4 | BandDialog 5 | 6 | This dialog handles the adaption of a band parameter. Therefore, reads the available bands from the backend and 7 | lets the user choose on them. On submission it returns the values back to the AdaptDialog parent. 8 | 9 | author : 2020 by Bernhard Goesswein 10 | email : bernhard.goesswein@geo.tuwien.ac.at 11 | ***************************************************************************/ 12 | """ 13 | import os 14 | 15 | from qgis.PyQt import uic 16 | 17 | from PyQt5 import QtWidgets 18 | 19 | import qgis.PyQt.QtCore as QtCore 20 | from PyQt5.QtCore import Qt 21 | from PyQt5.QtWidgets import QApplication, QListWidgetItem 22 | 23 | ######################################################################################################################## 24 | ######################################################################################################################## 25 | 26 | # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer 27 | FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'band_dialog.ui')) 28 | 29 | 30 | class BandDialog(QtWidgets.QDialog, FORM_CLASS): 31 | """ 32 | This dialog class has the purpose to adaption of a band parameter. Therefore, reads the available bands 33 | from the backend and lets the user choose on them. On submission it returns the values back 34 | to the AdaptDialog parent. 35 | """ 36 | def __init__(self, parent=None, iface=None, bands=None, all_bands=None): 37 | """Constructor method 38 | """ 39 | super(BandDialog, self).__init__(parent) 40 | # Set up the user interface from Designer through FORM_CLASS. 41 | # After self.setupUi() you can access any designer object by doing 42 | # self., and you can use autoconnect slots - see 43 | # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html 44 | # #widgets-and-dialogs-with-auto-connect 45 | 46 | QApplication.setStyle("cleanlooks") 47 | 48 | self.iface = iface 49 | self.called = False 50 | self.called2 = False 51 | self.processes = None 52 | 53 | self.setupUi(self) 54 | 55 | self.label_11.setEnabled(True) # Add Bands 56 | 57 | self.bands = bands 58 | self.all_bands = all_bands 59 | 60 | self.init_band_list() 61 | 62 | self.buttonBox.accepted.connect(self.accept_dialog) 63 | 64 | def init_band_list(self): 65 | """ 66 | Initializes the band list in the dialog, by adding the ands to the WidgetList object and sorting them. 67 | It also activates the checkboxes of the currently selected bands. 68 | """ 69 | self.bandsListWidget.clear() 70 | 71 | for band in self.all_bands: 72 | # Set Checkbox before band 73 | item = QListWidgetItem(self.bandsListWidget) 74 | item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) 75 | if band in self.bands: 76 | item.setCheckState(Qt.Checked) 77 | else: 78 | item.setCheckState(Qt.Unchecked) 79 | item.setText(str(band)) 80 | 81 | self.bandsListWidget.sortItems() 82 | 83 | def accept_dialog(self): 84 | """ 85 | Gets called when the user selected the bands and applies the selection. Calls a method from the JobAdaptDialog 86 | parent to pass the selection information. 87 | """ 88 | band_list = [] 89 | for index in range(self.bandsListWidget.count()): 90 | if self.bandsListWidget.item(index).checkState() == Qt.Checked: 91 | band_list.append(self.bandsListWidget.item(index).text()) 92 | self.parent().receive_bands(band_list) 93 | -------------------------------------------------------------------------------- /band_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | BandDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 278 10 | 279 11 | 12 | 13 | 14 | Set Choice of Bands 15 | 16 | 17 | 18 | 19 | 20 | <html><head/><body><p><span style=" font-weight:600;">Chosen Bands</span></p></body></html> 21 | 22 | 23 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 24 | 25 | 26 | 27 | 28 | 29 | 30 | QAbstractItemView::MultiSelection 31 | 32 | 33 | 34 | 35 | 36 | 37 | Qt::Horizontal 38 | 39 | 40 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | buttonBox 50 | accepted() 51 | BandDialog 52 | accept() 53 | 54 | 55 | 248 56 | 254 57 | 58 | 59 | 157 60 | 274 61 | 62 | 63 | 64 | 65 | buttonBox 66 | rejected() 67 | BandDialog 68 | reject() 69 | 70 | 71 | 316 72 | 260 73 | 74 | 75 | 286 76 | 274 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /drawPoly.py: -------------------------------------------------------------------------------- 1 | """ 2 | /*************************************************************************** 3 | DrawPolygon 4 | 5 | This class is responsible for drawing a polygon on the map and returning the coordinates of it. 6 | 7 | author : 2020 by Bernhard Goesswein 8 | email : bernhard.goesswein@geo.tuwien.ac.at 9 | ***************************************************************************/ 10 | """ 11 | from qgis.gui import QgsMapTool, QgsRubberBand 12 | from qgis.core import QgsWkbTypes 13 | from PyQt5.QtCore import pyqtSignal, Qt 14 | from PyQt5.QtGui import QKeySequence, QColor 15 | 16 | 17 | class DrawPolygon(QgsMapTool): 18 | """ 19 | This class is responsible for drawing a polygon on the map and returning the coordinates of it. 20 | """ 21 | selectionDone = pyqtSignal() 22 | move = pyqtSignal() 23 | 24 | def __init__(self, iface, parent): 25 | """ 26 | Initialize the draw polygon class 27 | :param iface: Interface to be displayed 28 | :param parent: Parent dialog, which initialized the class (should be JobAdaptDialog) 29 | """ 30 | canvas = iface 31 | QgsMapTool.__init__(self, canvas) 32 | self.canvas = canvas 33 | self.iface = iface 34 | self.parent = parent 35 | self.status = 0 36 | self.rb = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) 37 | self.rb.setColor(QColor(254, 178, 76, 63)) 38 | 39 | def keyPressEvent(self, e): 40 | """ 41 | Called if a keyboard key got pressed 42 | :param e: Event 43 | """ 44 | if e.matches(QKeySequence.Undo): 45 | if self.rb.numberOfVertices() > 1: 46 | self.rb.removeLastPoint() 47 | 48 | def canvasPressEvent(self, e): 49 | """ 50 | Called if a mouse button got pressed on the map canvas. 51 | :param e: Event 52 | """ 53 | if e.button() == Qt.LeftButton: 54 | if self.status == 0: 55 | self.rb.reset(QgsWkbTypes.PolygonGeometry) 56 | self.status = 1 57 | self.rb.addPoint(self.toMapCoordinates(e.pos())) 58 | else: 59 | if self.rb.numberOfVertices() > 2: 60 | self.status = 0 61 | self.selectionDone.emit() 62 | geometry = self.rb.asGeometry() 63 | self.parent.draw_poly(geometry) 64 | else: 65 | self.reset() 66 | 67 | def canvasMoveEvent(self, e): 68 | """ 69 | Called if a mouse button got pressed on the map canvas. 70 | :param e: Event 71 | """ 72 | if self.rb.numberOfVertices() > 0 and self.status == 1: 73 | self.rb.removeLastPoint(0) 74 | self.rb.addPoint(self.toMapCoordinates(e.pos())) 75 | self.move.emit() 76 | 77 | def reset(self): 78 | """ 79 | Reseting the polygon 80 | """ 81 | self.status = 0 82 | self.rb.reset(True) 83 | 84 | def deactivate(self): 85 | """ 86 | Deactivate the polygon 87 | """ 88 | self.rb.reset(True) 89 | QgsMapTool.deactivate(self) 90 | -------------------------------------------------------------------------------- /drawRect.py: -------------------------------------------------------------------------------- 1 | """ 2 | /*************************************************************************** 3 | DrawRectangle 4 | 5 | This class is responsible for drawing a rectangle on the map and returning the coordinates of it. 6 | 7 | author : 2020 by Bernhard Goesswein 8 | email : bernhard.goesswein@geo.tuwien.ac.at 9 | ***************************************************************************/ 10 | """ 11 | from qgis.gui import QgsMapTool, QgsRubberBand 12 | from qgis.core import QgsWkbTypes, QgsPointXY 13 | from qgis.PyQt.QtGui import QColor 14 | from qgis.PyQt.QtCore import pyqtSignal 15 | 16 | 17 | class DrawRectangle(QgsMapTool): 18 | """ 19 | This class is responsible for drawing a rectangle on the map and returning the coordinates of it. 20 | """ 21 | rectangleCreated = pyqtSignal(float, float, float, float) 22 | 23 | def __init__(self, canvas, parent): 24 | """ 25 | Initialize the draw rectangle class 26 | :param canvas: Canvas to be displayed 27 | :param parent: Parent dialog, which initialized the class (should be JobAdaptDialog) 28 | """ 29 | QgsMapTool.__init__(self, canvas) 30 | 31 | self.canvas = canvas 32 | self.active = False 33 | self.parent = parent 34 | self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) 35 | self.rubberBand.setColor(QColor(254, 178, 76, 63)) 36 | self.rubberBand.setWidth(1) 37 | self.startPoint = None 38 | self.endPoint = None 39 | self.isEmittingPoint = False 40 | self.reset() 41 | 42 | def reset(self): 43 | """ 44 | Reseting the rectangle 45 | """ 46 | self.startPoint = self.endPoint = None 47 | self.isEmittingPoint = False 48 | self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) 49 | 50 | def canvasPressEvent(self, e): 51 | """ 52 | Called if a mouse button got pressed on the map canvas, taking the starting point of the rectangle. 53 | :param e: Event 54 | """ 55 | self.startPoint = self.toMapCoordinates(e.pos()) 56 | self.endPoint = self.startPoint 57 | self.isEmittingPoint = True 58 | self.show_rect(self.startPoint, self.endPoint) 59 | 60 | def canvasReleaseEvent(self, e): 61 | """ 62 | Called if a mouse button got released on the map canvas, finishing the selection and 63 | returning to the parent dialog. 64 | :param e: Event 65 | """ 66 | self.isEmittingPoint = False 67 | self.rubberBand.show() 68 | self.parent.draw_rect(self.startPoint.x(), self.startPoint.y(), self.endPoint.x(), self.endPoint.y()) 69 | 70 | def canvasMoveEvent(self, e): 71 | """ 72 | Called if a mouse moves over the map canvas, taking the end point of the rectangle. 73 | :param e: Event 74 | """ 75 | if not self.isEmittingPoint: 76 | return 77 | self.endPoint = self.toMapCoordinates(e.pos()) 78 | self.show_rect(self.startPoint, self.endPoint) 79 | 80 | def show_rect(self, start_point, end_point): 81 | """ 82 | Showing the rectangle with the given start and end point. 83 | :param start_point: Point: Starting point of the rectangle drawing 84 | :param end_point: Point: End point of the rectangle drawing 85 | """ 86 | self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) 87 | if start_point.x() == end_point.x() or start_point.y() == end_point.y(): 88 | return 89 | point1 = QgsPointXY(start_point.x(), start_point.y()) 90 | point2 = QgsPointXY(start_point.x(), end_point.y()) 91 | point3 = QgsPointXY(end_point.x(), end_point.y()) 92 | point4 = QgsPointXY(end_point.x(), start_point.y()) 93 | 94 | self.rubberBand.addPoint(point1, False) 95 | self.rubberBand.addPoint(point2, False) 96 | self.rubberBand.addPoint(point3, False) 97 | self.rubberBand.addPoint(point4, True) # true to update canvas 98 | self.rubberBand.show() 99 | 100 | def deactivate(self): 101 | """ 102 | Deactivating the rectangle 103 | """ 104 | self.rubberBand.hide() 105 | QgsMapTool.deactivate(self) 106 | -------------------------------------------------------------------------------- /examples/create_processgraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/examples/create_processgraph.png -------------------------------------------------------------------------------- /examples/job_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/examples/job_list.png -------------------------------------------------------------------------------- /help/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/template_class.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/template_class.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/template_class" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/template_class" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /help/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^` where ^ is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. text to make text files 30 | echo. man to make manual pages 31 | echo. changes to make an overview over all changed/added/deprecated items 32 | echo. linkcheck to check all external links for integrity 33 | echo. doctest to run all doctests embedded in the documentation if enabled 34 | goto end 35 | ) 36 | 37 | if "%1" == "clean" ( 38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 39 | del /q /s %BUILDDIR%\* 40 | goto end 41 | ) 42 | 43 | if "%1" == "html" ( 44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 45 | echo. 46 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 47 | goto end 48 | ) 49 | 50 | if "%1" == "dirhtml" ( 51 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 52 | echo. 53 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 54 | goto end 55 | ) 56 | 57 | if "%1" == "singlehtml" ( 58 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 59 | echo. 60 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 61 | goto end 62 | ) 63 | 64 | if "%1" == "pickle" ( 65 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 66 | echo. 67 | echo.Build finished; now you can process the pickle files. 68 | goto end 69 | ) 70 | 71 | if "%1" == "json" ( 72 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 73 | echo. 74 | echo.Build finished; now you can process the JSON files. 75 | goto end 76 | ) 77 | 78 | if "%1" == "htmlhelp" ( 79 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 80 | echo. 81 | echo.Build finished; now you can run HTML Help Workshop with the ^ 82 | .hhp project file in %BUILDDIR%/htmlhelp. 83 | goto end 84 | ) 85 | 86 | if "%1" == "qthelp" ( 87 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 88 | echo. 89 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 90 | .qhcp project file in %BUILDDIR%/qthelp, like this: 91 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\template_class.qhcp 92 | echo.To view the help file: 93 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\template_class.ghc 94 | goto end 95 | ) 96 | 97 | if "%1" == "devhelp" ( 98 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 99 | echo. 100 | echo.Build finished. 101 | goto end 102 | ) 103 | 104 | if "%1" == "epub" ( 105 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 106 | echo. 107 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 108 | goto end 109 | ) 110 | 111 | if "%1" == "latex" ( 112 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 113 | echo. 114 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 115 | goto end 116 | ) 117 | 118 | if "%1" == "text" ( 119 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 120 | echo. 121 | echo.Build finished. The text files are in %BUILDDIR%/text. 122 | goto end 123 | ) 124 | 125 | if "%1" == "man" ( 126 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 127 | echo. 128 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 129 | goto end 130 | ) 131 | 132 | if "%1" == "changes" ( 133 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 134 | echo. 135 | echo.The overview file is in %BUILDDIR%/changes. 136 | goto end 137 | ) 138 | 139 | if "%1" == "linkcheck" ( 140 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 141 | echo. 142 | echo.Link check complete; look for any errors in the above output ^ 143 | or in %BUILDDIR%/linkcheck/output.txt. 144 | goto end 145 | ) 146 | 147 | if "%1" == "doctest" ( 148 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 149 | echo. 150 | echo.Testing of doctests in the sources finished, look at the ^ 151 | results in %BUILDDIR%/doctest/output.txt. 152 | goto end 153 | ) 154 | 155 | :end 156 | -------------------------------------------------------------------------------- /help/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # OpenEO documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Feb 12 17:11:03 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.todo', 'sphinx.ext.imgmath', 'sphinx.ext.viewcode'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'OpenEO' 44 | copyright = u'2013, Bernhard Goesswein' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.1' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.1' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = [] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_TemplateModuleNames = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'TemplateClassdoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | # The paper size ('letter' or 'a4'). 173 | #latex_paper_size = 'letter' 174 | 175 | # The font size ('10pt', '11pt' or '12pt'). 176 | #latex_font_size = '10pt' 177 | 178 | # Grouping the document tree into LaTeX files. List of tuples 179 | # (source start file, target name, title, author, documentclass [howto/manual]). 180 | latex_documents = [ 181 | ('index', 'OpenEO.tex', u'OpenEO Documentation', 182 | u'Bernhard Goesswein', 'manual'), 183 | ] 184 | 185 | # The name of an image file (relative to this directory) to place at the top of 186 | # the title page. 187 | #latex_logo = None 188 | 189 | # For "manual" documents, if this is true, then toplevel headings are parts, 190 | # not chapters. 191 | #latex_use_parts = False 192 | 193 | # If true, show page references after internal links. 194 | #latex_show_pagerefs = False 195 | 196 | # If true, show URL addresses after external links. 197 | #latex_show_urls = False 198 | 199 | # Additional stuff for the LaTeX preamble. 200 | #latex_preamble = '' 201 | 202 | # Documents to append as an appendix to all manuals. 203 | #latex_appendices = [] 204 | 205 | # If false, no module index is generated. 206 | #latex_domain_indices = True 207 | 208 | 209 | # -- Options for manual page output -------------------------------------------- 210 | 211 | # One entry per manual page. List of tuples 212 | # (source start file, name, description, authors, manual section). 213 | man_pages = [ 214 | ('index', 'TemplateClass', u'OpenEO Documentation', 215 | [u'Bernhard Goesswein'], 1) 216 | ] 217 | -------------------------------------------------------------------------------- /help/source/index.rst: -------------------------------------------------------------------------------- 1 | .. OpenEO documentation master file, created by 2 | sphinx-quickstart on Sun Feb 12 17:11:03 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to OpenEO's documentation! 7 | ============================================ 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | Indices and tables 15 | ================== 16 | 17 | * :ref:`genindex` 18 | * :ref:`modindex` 19 | * :ref:`search` 20 | 21 | -------------------------------------------------------------------------------- /i18n/af.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @default 5 | 6 | 7 | Good morning 8 | Goeie more 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /images/deleteFinalBtn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/deleteFinalBtn.png -------------------------------------------------------------------------------- /images/delete_job.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/delete_job.png -------------------------------------------------------------------------------- /images/display_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 11 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /images/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/download.png -------------------------------------------------------------------------------- /images/edit_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/edit_icon.png -------------------------------------------------------------------------------- /images/execute_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | play_circle_filled_white 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/icon.png -------------------------------------------------------------------------------- /images/icon_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/icon_new.png -------------------------------------------------------------------------------- /images/icon_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/icon_old.png -------------------------------------------------------------------------------- /images/info_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/info_icon.png -------------------------------------------------------------------------------- /images/openEO_plugin_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/openEO_plugin_header.png -------------------------------------------------------------------------------- /images/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/pause.png -------------------------------------------------------------------------------- /images/processGraph_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/processGraph_icon.png -------------------------------------------------------------------------------- /images/reload_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/reload_icon.png -------------------------------------------------------------------------------- /images/stop-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/stop-button.png -------------------------------------------------------------------------------- /images/user_manual_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-EO/openeo-qgis-plugin/9a300c1bf51f46d7acb868226a3ff4606afbdc39/images/user_manual_icon.png -------------------------------------------------------------------------------- /job_adapt_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | TemporalDialog 4 | 5 | 6 | Qt::WindowModal 7 | 8 | 9 | 10 | 0 11 | 0 12 | 1276 13 | 527 14 | 15 | 16 | 17 | 18 | 0 19 | 0 20 | 21 | 22 | 23 | Qt::NoFocus 24 | 25 | 26 | Adapt Job 27 | 28 | 29 | false 30 | 31 | 32 | false 33 | 34 | 35 | 36 | 37 | 38 | Job Editor 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | QLayout::SetDefaultConstraint 47 | 48 | 49 | 50 | 51 | true 52 | 53 | 54 | 55 | 0 56 | 0 57 | 58 | 59 | 60 | 61 | true 62 | 63 | 64 | 65 | <html><head/><body><p><span style=" font-weight:600;">Title</span></p></body></html> 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | 77 | 78 | 79 | 0 80 | 0 81 | 82 | 83 | 84 | 85 | true 86 | 87 | 88 | 89 | <html><head/><body><p><span style=" font-weight:600;">Description</span></p><p><br/></p></body></html> 90 | 91 | 92 | 93 | 94 | 95 | 96 | true 97 | 98 | 99 | 100 | 0 101 | 0 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | true 114 | 115 | 116 | 117 | 0 118 | 0 119 | 120 | 121 | 122 | 123 | 16777215 124 | 30 125 | 126 | 127 | 128 | 129 | true 130 | 131 | 132 | 133 | <html><head/><body><p><span style=" font-weight:600;">Process Graph</span></p></body></html> 134 | 135 | 136 | 137 | 138 | 139 | 140 | QAbstractItemView::SelectRows 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | Undo Changes 150 | 151 | 152 | 153 | 154 | 155 | 156 | Raw Graph 157 | 158 | 159 | 160 | 161 | 162 | 163 | Qt::Horizontal 164 | 165 | 166 | 167 | 40 168 | 20 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | Process Editor 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | Add Process 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | true 220 | 221 | 222 | 223 | true 224 | 225 | 226 | 227 | <html><head/><body><p><span style=" font-weight:600;">Process</span></p></body></html> 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 75 243 | true 244 | 245 | 246 | 247 | Result Process 248 | 249 | 250 | 251 | 252 | 253 | 254 | Qt::Horizontal 255 | 256 | 257 | 258 | 40 259 | 20 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | false 268 | 269 | 270 | Clone Process 271 | 272 | 273 | 274 | 275 | 276 | 277 | false 278 | 279 | 280 | Save Process 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | Manage Job 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | Adapt Job 301 | 302 | 303 | 304 | 305 | 306 | 307 | Qt::Horizontal 308 | 309 | 310 | 311 | 40 312 | 20 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | Create New Job 321 | 322 | 323 | 324 | 325 | 326 | 327 | Qt::Horizontal 328 | 329 | 330 | 331 | 40 332 | 20 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | Cancel 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | -------------------------------------------------------------------------------- /job_detail_dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | /*************************************************************************** 4 | JobDetailDialog 5 | 6 | This class is responsible for showing detailed metadata on an existing openEO job. 7 | 8 | author : 2020 by Bernhard Goesswein 9 | email : bernhard.goesswein@geo.tuwien.ac.at 10 | ***************************************************************************/ 11 | """ 12 | import os 13 | 14 | 15 | from qgis.PyQt import uic 16 | from PyQt5.QtCore import Qt 17 | from PyQt5 import QtWidgets, QtCore 18 | 19 | from PyQt5.QtWidgets import QApplication 20 | from qgis.PyQt.QtWidgets import QTableWidgetItem, QApplication 21 | 22 | from PyQt5.QtGui import QFont 23 | from .job_adapt_dialog import JobAdaptDialog 24 | 25 | ######################################################################################################################## 26 | ######################################################################################################################## 27 | 28 | # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer 29 | FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'job_detail_dialog.ui')) 30 | 31 | 32 | class JobDetailDialog(QtWidgets.QDialog, FORM_CLASS): 33 | """ 34 | This class is responsible for showing detailed metadata on an existing openEO job. 35 | """ 36 | def __init__(self, parent=None, iface=None, job=None, backend=None): 37 | """ 38 | Constructor method: Initializing the button behaviours and the metadata fields. 39 | :param parent: parent dialog of this dialog (e.g. OpenEODialog). 40 | :param iface: Interface to show the dialog. 41 | :param job: Job: Job that should be displayed in detail. 42 | :param backend: Backend: Currently connected backend. 43 | """ 44 | super(JobDetailDialog, self).__init__(parent) 45 | 46 | QApplication.setStyle("cleanlooks") 47 | 48 | self.iface = iface 49 | self.setupUi(self) 50 | 51 | self.backend = backend 52 | 53 | self.log_info = self.backend.job_log(job.id) 54 | 55 | self.job = job 56 | self.init_table() 57 | self.fill_table() 58 | 59 | self.dlg = None 60 | 61 | self.cancelButton.clicked.connect(self.close) 62 | # self.adaptButton.clicked.connect(self.adapt_job) 63 | 64 | # def adapt_job(self): 65 | # """ 66 | # Starts an adaption dialog to adapt the current job. 67 | # """ 68 | # self.dlg = JobAdaptDialog(iface=self.iface, job=self.job, backend=self.backend) 69 | # self.dlg.setWindowFlags(Qt.WindowStaysOnTopHint) 70 | # self.dlg.show() 71 | # self.close() 72 | 73 | def init_table(self): 74 | """ 75 | Initializes the job infor table. 76 | """ 77 | self.jobInfoTableWidget.clear() 78 | self.jobInfoTableWidget.setColumnCount(2) 79 | self.jobInfoTableWidget.setHorizontalHeaderLabels(['Property', 'Value']) 80 | header = self.jobInfoTableWidget.horizontalHeader() 81 | self.jobInfoTableWidget.setSortingEnabled(True) 82 | self.jobInfoTableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) 83 | header.setSectionResizeMode(0, QtWidgets.QHeaderView.Interactive) 84 | header.setSectionResizeMode(1, QtWidgets.QHeaderView.Interactive) 85 | 86 | def set_value_widget(self, value, row): 87 | """ 88 | Sets the property value into the given row of the job table. 89 | :param value: str: Property value of the row. 90 | :param row: int: Row number to but the value in 91 | """ 92 | qitem = QTableWidgetItem(str(value)) 93 | qitem.setFlags(QtCore.Qt.ItemIsEnabled) 94 | self.jobInfoTableWidget.setItem(row, 1, qitem) 95 | 96 | def set_property_widget(self, prop, row): 97 | """ 98 | Sets the property name into the given row of the job table. 99 | :param prop: str: Property name of the row. 100 | :param row: int: Row number to but the value in 101 | """ 102 | qitem = QTableWidgetItem(str(prop)) 103 | font = QFont() 104 | font.setBold(True) 105 | qitem.setFont(font) 106 | qitem.setFlags(QtCore.Qt.ItemIsEnabled) 107 | self.jobInfoTableWidget.setItem(row, 0, qitem) 108 | 109 | def fill_table(self): 110 | """ 111 | Fills the table with the properties and their values. 112 | """ 113 | property_list = [] 114 | if self.job.id: 115 | property_list.append(("Id", self.job.id)) 116 | 117 | if self.job.title: 118 | property_list.append(("Title", self.job.title)) 119 | 120 | if self.job.description: 121 | property_list.append(("Description", self.job.description)) 122 | 123 | if self.job.process: 124 | property_list.append(("Process", str(self.job.process))) 125 | 126 | if self.job.status: 127 | property_list.append(("Status", self.job.status)) 128 | 129 | if self.job.progress: 130 | property_list.append(("Progress", self.job.progress)) 131 | 132 | if self.job.created: 133 | property_list.append(("Created", self.job.created)) 134 | 135 | if self.job.updated: 136 | property_list.append(("Updated", self.job.updated)) 137 | 138 | if self.job.plan: 139 | property_list.append(("Plan", self.job.plan)) 140 | 141 | if self.job.costs: 142 | property_list.append(("Costs", self.job.costs)) 143 | 144 | if self.job.budget: 145 | property_list.append(("Budget", self.job.budget)) 146 | 147 | if self.log_info: 148 | property_list.append(("Log Entries:", "see below")) 149 | for log in self.log_info.get("logs"): 150 | property_list.append((log.get("level"), "{}".format(log.get("message")))) 151 | 152 | self.jobInfoTableWidget.setRowCount(len(property_list)) 153 | row = 0 154 | for prop in property_list: 155 | self.set_property_widget(prop[0], row) 156 | self.set_value_widget(prop[1], row) 157 | row += 1 158 | 159 | self.jobInfoTableWidget.resizeColumnsToContents() 160 | -------------------------------------------------------------------------------- /job_detail_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | JobDetailDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 376 10 | 359 11 | 12 | 13 | 14 | Qt::NoFocus 15 | 16 | 17 | Job Details 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Cancel 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /login_dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | /*************************************************************************** 4 | LoginDialog 5 | 6 | This class is responsible for showing the login window and to let the user log in to an openEO backend. 7 | 8 | author : 2020 by Bernhard Goesswein 9 | email : bernhard.goesswein@geo.tuwien.ac.at 10 | ***************************************************************************/ 11 | """ 12 | import os 13 | 14 | from qgis.PyQt import uic 15 | 16 | from PyQt5 import QtWidgets 17 | from PyQt5.QtWidgets import QApplication 18 | from PyQt5.QtGui import QIcon 19 | from qgis.PyQt.QtWidgets import QApplication 20 | from PyQt5.QtCore import Qt 21 | 22 | from qgis.PyQt.QtGui import QIcon 23 | from .openeo_connector_dialog import OpenEODialog 24 | from .models.backend import Backend 25 | from .models.openeohub import get_hub_backends 26 | 27 | from .utils.logging import warning 28 | ######################################################################################################################## 29 | ######################################################################################################################## 30 | 31 | # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer 32 | FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'login_dialog.ui')) 33 | 34 | 35 | class LoginDialog(QtWidgets.QDialog, FORM_CLASS): 36 | """ 37 | This class is responsible for showing the login window and to let the user log in to an openEO backend. 38 | """ 39 | def __init__(self, parent=None, iface=None, openeo=None): 40 | """ 41 | Constructor method: Initializing the button behaviours and the backend combobox. 42 | :param parent: parent dialog of this dialog (e.g. OpenEODialog). 43 | :param iface: Interface to show the dialog. 44 | """ 45 | super(LoginDialog, self).__init__(parent) 46 | 47 | QApplication.setStyle("cleanlooks") 48 | 49 | self.openeo = openeo 50 | 51 | self.iface = iface 52 | self.called = False 53 | self.called2 = False 54 | self.processes = None 55 | 56 | self.setupUi(self) 57 | self.backends = [] 58 | try: 59 | self.backends = get_hub_backends() 60 | self.set_backend_urls() 61 | except: 62 | warning(self.iface, "The plugin was not able to connect to openEO Hub. " 63 | "Are you connected to the internet?") 64 | 65 | self.loginButton.clicked.connect(self.login) 66 | self.dlg = None 67 | 68 | self.versionBox.stateChanged.connect(self.version_checkbox_changed) 69 | 70 | def set_backend_urls(self, latest_version=True): 71 | """ 72 | Loads the backend urls, already retrieved in the constructor, into the combo box. If latest_version is set, it 73 | will show the backend names, otherwise it shows the urls of all versions. 74 | :param latest_version: bool: show only latest versions or all of the backends. 75 | """ 76 | self.backendEdit.clear() 77 | if latest_version: 78 | for backend in self.backends: 79 | self.backendEdit.addItem(backend.name) 80 | else: 81 | for backend in self.backends: 82 | self.backendEdit.addItems(backend.get_all_urls()) 83 | 84 | def version_checkbox_changed(self): 85 | """ 86 | Adapt the backend combo box according to the version check box. 87 | """ 88 | self.set_backend_urls(latest_version=self.versionBox.isChecked()) 89 | 90 | def get_current_url(self): 91 | """ 92 | Returns the currently selected url, so either the latest version of the selected backend or just 93 | the url in the backend url field. 94 | """ 95 | if self.versionBox.isChecked(): 96 | for bckend in self.backends: 97 | if bckend.name == self.backendEdit.currentText(): 98 | return bckend.get_latest_version() 99 | 100 | return self.backendEdit.currentText() 101 | 102 | def connect(self): 103 | """ 104 | Connect to the backend via the given credentials. It will connect via BasicAuthentication and Bearertoken. 105 | If there are no credentials, it connects to the backend without authentication. 106 | This method also loads all collections and processes from the backend. 107 | """ 108 | url = self.get_current_url() 109 | pwd = self.passwordEdit.text() 110 | user = self.usernameEdit.text() 111 | if user == "": 112 | user = None 113 | if pwd == "": 114 | pwd = None 115 | 116 | backend = Backend(url=url) 117 | 118 | if not backend: 119 | warning(self.iface, "Connection failed, the backend might not be available at the moment!") 120 | return None 121 | 122 | auth = backend.login(username=user, password=pwd) 123 | 124 | if not auth: 125 | warning(self.iface, "Authentication failed!") 126 | return None 127 | 128 | return backend 129 | 130 | def login(self): 131 | """ 132 | Logs the user into the backend and starts the main openEO dialog, also closes this login dialog. 133 | """ 134 | self.openeo.login() 135 | # backend = self.connect() 136 | # 137 | # if not backend: 138 | # return 139 | # 140 | # self.dlg = OpenEODialog(interface=self.iface, backend=backend) 141 | # self.dlg.infoBtn2.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 'images/info_icon.png'))) 142 | # self.dlg.refreshButton.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 'images/reload_icon.png'))) 143 | # self.dlg.refreshButton_service.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 'images/reload_icon.png'))) 144 | # self.dlg.jobsManualBtn.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 145 | # 'images/user_manual_icon.png'))) 146 | # self.dlg.servicesManualBtn.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 147 | # 'images/user_manual_icon.png'))) 148 | # self.dlg.explorativeManualBtn.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 149 | # 'images/user_manual_icon.png'))) 150 | # # self.dlg.setWindowFlags(Qt.WindowStaysOnTopHint) 151 | # self.dlg.show() 152 | # # self.close() 153 | -------------------------------------------------------------------------------- /login_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SpatialDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 532 10 | 216 11 | 12 | 13 | 14 | openEO Connector 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 75 24 | true 25 | 26 | 27 | 28 | Choose Backend URL 29 | 30 | 31 | 32 | 33 | 34 | 35 | true 36 | 37 | 38 | 39 | 40 | 41 | 42 | Qt::RightToLeft 43 | 44 | 45 | Only Latest versions 46 | 47 | 48 | true 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 75 57 | true 58 | 59 | 60 | 61 | Username 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 16777215 70 | 16777215 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 75 80 | true 81 | 82 | 83 | 84 | Password 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | false 93 | 94 | 95 | 96 | QLineEdit::Password 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | ../../../../../../../../../../../../../../../../.designer/backup/icon_new.png 114 | 115 | 116 | true 117 | 118 | 119 | 120 | 121 | 122 | 123 | Qt::Vertical 124 | 125 | 126 | 127 | 20 128 | 40 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | Login 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /metadata.txt: -------------------------------------------------------------------------------- 1 | # This file contains metadata for your plugin. 2 | 3 | # This file should be included when you package your plugin.# Mandatory items: 4 | 5 | [general] 6 | name=OpenEO 7 | qgisMinimumVersion=3.0 8 | description=Plugin to access openEO compliant backends. 9 | version=1.0 10 | author=Bernhard Goesswein, Nina Gnann 11 | email=bernhard.goesswein@geo.tuwien.ac.at 12 | 13 | about=The plugin can be used to access backends compliant with the openEO specification. It can be used to define jobs, execute them at the backend and visualize the results on QGIS. 14 | 15 | tracker=https://github.com/Open-EO/openeo-qgis-plugin/issues 16 | repository=https://github.com/Open-EO/openeo-qgis-plugin 17 | # End of mandatory metadata 18 | 19 | # Recommended items: 20 | 21 | hasProcessingProvider=no 22 | # Uncomment the following line and add your changelog: 23 | # changelog= 24 | 25 | # Tags are comma separated with spaces allowed 26 | tags=project, remote sensing, web, webservice, wms 27 | 28 | homepage=https://github.com/Open-EO/openeo-qgis-plugin 29 | category=Plugins 30 | icon=icon_new.png 31 | # experimental flag 32 | experimental=False 33 | 34 | # deprecated flag (applies to the whole plugin, not just a single version) 35 | deprecated=False 36 | 37 | # Since QGIS 3.8, a comma separated list of plugins to be installed 38 | # (or upgraded) can be specified. 39 | # Check the documentation for more information. 40 | # plugin_dependencies= 41 | 42 | Category of the plugin: Raster, Vector, Database or Web 43 | # category= 44 | 45 | # If the plugin can run on QGIS Server. 46 | server=False 47 | 48 | -------------------------------------------------------------------------------- /models/graphbuilder.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from typing import Dict, Union 3 | 4 | class GraphBuilder(): 5 | 6 | def __init__(self, graph=None): 7 | """ 8 | Create a process graph builder. 9 | If a graph is provided, its nodes will be added to this builder, this does not necessarily preserve id's of the nodes. 10 | 11 | :param graph: Dict : Optional, existing process graph 12 | """ 13 | self.processes = {} 14 | self.id_counter = {} 15 | if graph is not None: 16 | self._merge_processes(graph) 17 | 18 | def copy(self): 19 | return GraphBuilder(self.processes) 20 | 21 | def add_process(self, process_id, result=None, **args): 22 | process_id = self.process(process_id, args) 23 | if result is not None: 24 | self.processes[process_id]["result"] = result 25 | return process_id 26 | 27 | def process(self, process_id, args): 28 | """ 29 | Add a process and return the id. Do not add a new process if it already exists in the graph. 30 | 31 | :param process_id: 32 | :param args: 33 | :return: 34 | """ 35 | new_process = { 36 | 'process_id': process_id, 37 | 'arguments': args, 38 | 'result': False 39 | } 40 | # try: 41 | # existing_id = list(self.processes.keys())[list(self.processes.values()).index(new_process)] 42 | # return existing_id 43 | # except ValueError as e: 44 | # pass 45 | id = self._generate_id(process_id) 46 | self.processes[id] = new_process 47 | return id 48 | 49 | def _generate_id(self, name: str): 50 | name = name.replace("_", "") 51 | if (not self.id_counter.get(name)): 52 | self.id_counter[name] = 1 53 | else: 54 | self.id_counter[name] += 1 55 | return name + str(self.id_counter[name]) 56 | 57 | def merge(self, other: 'GraphBuilder'): 58 | return GraphBuilder(self.processes)._merge_processes(other.processes) 59 | 60 | def _merge_processes(self, processes: Dict, return_key_map=False): 61 | # Maps original node key to new key in merged result 62 | key_map = {} 63 | node_refs = [] 64 | for key, process in sorted(processes.items()): 65 | process_id = process['process_id'] 66 | args = process['arguments'] 67 | result = process.get('result', None) 68 | args_copy = copy.deepcopy(args) 69 | id = self.process(process_id, args_copy) 70 | if id != key: 71 | key_map[key] = id 72 | node_refs += self._extract_node_references(args_copy) 73 | 74 | if result is not None: 75 | self.processes[id]['result'] = result 76 | 77 | for node_ref in node_refs: 78 | old_node_id = node_ref['from_node'] 79 | if old_node_id in key_map: 80 | node_ref['from_node'] = key_map[old_node_id] 81 | 82 | if return_key_map: 83 | return self, key_map 84 | else: 85 | return self 86 | 87 | def _extract_node_references(self, arguments): 88 | node_ref_list = [] 89 | for argument in arguments.values(): 90 | if isinstance(argument, dict): 91 | if 'from_node' in argument: 92 | node_ref_list.append(argument) 93 | if isinstance(argument, list): 94 | for element in argument: 95 | if isinstance(element, dict): 96 | if 'from_node' in element: 97 | node_ref_list.append(element) 98 | return node_ref_list 99 | 100 | def find_result_node_id(self): 101 | result_node_ids = [k for k, v in self.processes.items() if v.get('result', False)] 102 | if len(result_node_ids) == 1: 103 | return result_node_ids[0] 104 | else: 105 | raise RuntimeError("Invalid list of result node id's: " + str(result_node_ids)) 106 | 107 | 108 | 109 | @classmethod 110 | def combine(cls, operator: str, first: Union['GraphBuilder', dict], second: Union['GraphBuilder', dict]): 111 | """Combine two GraphBuilders to a new merged one using the given operator""" 112 | merged = cls() 113 | 114 | def insert_builder(builder: GraphBuilder): 115 | nonlocal merged 116 | result_node = builder.find_result_node_id() 117 | _, key_map = merged._merge_processes(builder.processes, return_key_map=True) 118 | key = key_map.get(result_node, result_node) 119 | merged.processes[key]['result'] = False 120 | return {'from_node': key} 121 | 122 | if isinstance(first, GraphBuilder): 123 | first = insert_builder(first) 124 | assert isinstance(first, dict) 125 | if isinstance(second, GraphBuilder): 126 | second = insert_builder(second) 127 | assert isinstance(second, dict) 128 | 129 | merged.add_process(operator, result=True, data=[first, second]) 130 | return merged 131 | -------------------------------------------------------------------------------- /models/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import json 3 | 4 | 5 | def list_to_lang(list_arg): 6 | pretty_txt = "" 7 | 8 | if len(list_arg) > 0: 9 | pretty_txt = list_arg[0] 10 | 11 | for i in range(1, len(list_arg)-1): 12 | pretty_txt += ", {}".format(list_arg[i]) 13 | 14 | if pretty_txt: 15 | pretty_txt += " or {}".format(list_arg[-1]) 16 | 17 | return pretty_txt 18 | 19 | 20 | class HubJob: 21 | title = None 22 | process_graph = None 23 | 24 | def __init__(self, title, process_graph): 25 | self.title = title 26 | self.process_graph = process_graph 27 | 28 | def to_job(self): 29 | job = Job() 30 | job.process = Process() 31 | job.process.process_graph = json.loads(self.process_graph) 32 | job.title = self.title 33 | return job 34 | 35 | 36 | class Job: 37 | 38 | id = None 39 | title = None 40 | description = None 41 | process = None 42 | status = None 43 | progress = None 44 | created = None 45 | updated = None 46 | plan = None 47 | costs = None 48 | budget = None 49 | 50 | def __init__(self): 51 | pass 52 | 53 | def __str__(self): 54 | return "{} - {} - {} - {} - {} - {} - {} - {} - {} - {} - {}".format(str(self.id), str(self.title), 55 | str(self.description), str(self.process), 56 | str(self.status), str(self.progress), 57 | str(self.created), str(self.updated), 58 | str(self.plan), str(self.costs), 59 | str(self.budget)) 60 | 61 | def from_metadata(self, metadata, version): 62 | 63 | if version.at_least("1.0.0"): 64 | if "id" in metadata: 65 | self.id = metadata["id"] 66 | if "title" in metadata: 67 | self.title = metadata["title"] 68 | if "description" in metadata: 69 | self.description = metadata["description"] 70 | if "process" in metadata: 71 | self.process = Process() 72 | self.process.from_metadata(metadata["process"], version) 73 | if "status" in metadata: 74 | self.status = metadata["status"] 75 | if "progress" in metadata: 76 | self.progress = metadata["progress"] 77 | if "created" in metadata: 78 | try: 79 | self.created = datetime.strptime(metadata["created"].replace("Z", "")[:19], '%Y-%m-%dT%H:%M:%S') 80 | except ValueError: 81 | self.created = None 82 | if "updated" in metadata: 83 | try: 84 | self.updated = datetime.strptime(metadata["updated"].replace("Z", "")[:19], '%Y-%m-%dT%H:%M:%S') 85 | except ValueError: 86 | self.updated = None 87 | if "plan" in metadata: 88 | self.plan = metadata["plan"] 89 | if "costs" in metadata: 90 | self.costs = metadata["costs"] 91 | if "budget" in metadata: 92 | self.budget = metadata["budget"] 93 | else: 94 | if "id" in metadata: 95 | self.id = metadata["id"] 96 | if "title" in metadata: 97 | self.title = metadata["title"] 98 | if "description" in metadata: 99 | self.description = metadata["description"] 100 | if "process_graph" in metadata: 101 | self.process = Process() 102 | self.process.process_graph = metadata["process_graph"] 103 | if "status" in metadata: 104 | self.status = metadata["status"] 105 | if "progress" in metadata: 106 | self.progress = metadata["progress"] 107 | if "submitted" in metadata: 108 | try: 109 | self.created = datetime.strptime(metadata["submitted"].replace("Z", "")[:19], '%Y-%m-%dT%H:%M:%S') 110 | except ValueError: 111 | self.created = None 112 | if "updated" in metadata: 113 | try: 114 | self.updated = datetime.strptime(metadata["updated"].replace("Z", "")[:19], '%Y-%m-%dT%H:%M:%S') 115 | except ValueError: 116 | self.updated = None 117 | if "plan" in metadata: 118 | self.plan = metadata["plan"] 119 | if "costs" in metadata: 120 | self.costs = metadata["costs"] 121 | if "budget" in metadata: 122 | self.budget = metadata["budget"] 123 | 124 | 125 | class Service: 126 | id = None 127 | title = None 128 | description = None 129 | process = None 130 | url = None 131 | type = None 132 | enabled = None 133 | configuration = None 134 | attributes = None 135 | created = None 136 | plan = None 137 | costs = None 138 | budget = None 139 | 140 | def __init__(self): 141 | pass 142 | 143 | def from_metadata(self, metadata, version): 144 | 145 | if not isinstance(metadata, dict): 146 | return 147 | 148 | if version.at_least("1.0.0"): 149 | if "id" in metadata: 150 | self.id = metadata["id"] 151 | if "title" in metadata: 152 | self.title = metadata["title"] 153 | if "description" in metadata: 154 | self.description = metadata["description"] 155 | if "process" in metadata: 156 | self.process = Process() 157 | self.process.from_metadata(metadata["process"], version) 158 | if "url" in metadata: 159 | self.url = metadata["url"] 160 | if "type" in metadata: 161 | self.type = metadata["type"] 162 | if "enabled" in metadata: 163 | self.enabled = metadata["enabled"] 164 | if "configuration" in metadata: 165 | self.configuration = metadata["configuration"] 166 | if "attributes" in metadata: 167 | self.attributes = metadata["attributes"] 168 | if "created" in metadata: 169 | self.created = datetime.strptime(metadata["created"], '%Y-%m-%dT%H:%M:%SZ') 170 | if "plan" in metadata: 171 | self.plan = metadata["plan"] 172 | if "costs" in metadata: 173 | self.costs = metadata["costs"] 174 | if "budget" in metadata: 175 | self.budget = metadata["budget"] 176 | else: 177 | if "id" in metadata: 178 | self.id = metadata["id"] 179 | if "title" in metadata: 180 | self.title = metadata["title"] 181 | if "description" in metadata: 182 | self.description = metadata["description"] 183 | if "process_graph" in metadata: 184 | self.process = Process() 185 | self.process.process_graph = metadata["process_graph"] 186 | if "url" in metadata: 187 | self.url = metadata["url"] 188 | if "type" in metadata: 189 | self.type = metadata["type"] 190 | if "enabled" in metadata: 191 | self.enabled = metadata["enabled"] 192 | if "parameters" in metadata: 193 | self.attributes = metadata["parameters"] 194 | if "submitted" in metadata: 195 | self.created = datetime.strptime(metadata["submitted"], '%Y-%m-%dT%H:%M:%SZ') 196 | if "plan" in metadata: 197 | self.plan = metadata["plan"] 198 | if "costs" in metadata: 199 | self.costs = metadata["costs"] 200 | if "budget" in metadata: 201 | self.budget = metadata["budget"] 202 | 203 | 204 | class Process: 205 | 206 | id = None 207 | process_graph = {} 208 | summary = None 209 | description = None 210 | categories = [] 211 | parameters = [] 212 | returns = None 213 | deprecated = None 214 | experimental = None 215 | exceptions = None 216 | examples = [] 217 | links = [] 218 | 219 | def __init(self): 220 | pass 221 | 222 | def __str__(self): 223 | return str(self.id) 224 | 225 | def is_arg_mandatory(self, arg_name): 226 | for param in self.parameters: 227 | if param.name == arg_name: 228 | return not param.optional 229 | 230 | def get_return_type(self): 231 | schema = self.returns["schema"] 232 | if "subtype" in schema: 233 | return schema["subtype"] 234 | elif "type" in schema: 235 | if isinstance(schema["type"], list): 236 | return list_to_lang(schema["type"]) 237 | else: 238 | return schema["type"] 239 | elif isinstance(schema, list): 240 | type_list = [] 241 | for item in schema: 242 | if "subtype" in item: 243 | type_list.append(item["subtype"]) 244 | elif "type" in item: 245 | type_list.append(item["type"]) 246 | return list_to_lang(type_list) 247 | else: 248 | return schema["description"] 249 | 250 | def from_metadata(self, metadata, version): 251 | 252 | if version.at_least("1.0.0"): 253 | if "id" in metadata: 254 | self.id = metadata["id"] 255 | if "process_graph" in metadata: 256 | self.process_graph = metadata["process_graph"] 257 | if "summary" in metadata: 258 | self.summary = metadata["summary"] 259 | if "description" in metadata: 260 | self.description = metadata["description"] 261 | if "categories" in metadata: 262 | self.categories = metadata["categories"] 263 | if "parameters" in metadata: 264 | self.parameters = [] 265 | for metap in metadata["parameters"]: 266 | param = Parameter() 267 | param.from_metadata(metap, version) 268 | self.parameters.append(param) 269 | if "returns" in metadata: 270 | self.returns = metadata["returns"] 271 | if "deprecated" in metadata: 272 | self.deprecated = metadata["deprecated"] 273 | if "experimental" in metadata: 274 | self.experimental = metadata["experimental"] 275 | if "exceptions" in metadata: 276 | self.exceptions = metadata["exceptions"] 277 | if "examples" in metadata: 278 | self.examples = metadata["examples"] 279 | if "links" in metadata: 280 | self.links = metadata["links"] 281 | else: 282 | if "processes" in metadata: 283 | metadata = metadata["processes"] 284 | if "id" in metadata: 285 | self.id = metadata["id"] 286 | if "summary" in metadata: 287 | self.summary = metadata["summary"] 288 | if "description" in metadata: 289 | self.description = metadata["description"] 290 | if "categories" in metadata: 291 | self.categories = metadata["categories"] 292 | if "parameters" in metadata: 293 | self.parameters = [] 294 | for key, value in metadata["parameters"]: 295 | param = Parameter() 296 | param.from_metadata(value, version) 297 | param.name = key 298 | self.parameters.append(param) 299 | if "returns" in metadata: 300 | self.returns = metadata["returns"] 301 | if "deprecated" in metadata: 302 | self.deprecated = metadata["deprecated"] 303 | if "experimental" in metadata: 304 | self.experimental = metadata["experimental"] 305 | if "exceptions" in metadata: 306 | self.exceptions = metadata["exceptions"] 307 | if "examples" in metadata: 308 | self.examples = metadata["examples"] 309 | if "links" in metadata: 310 | self.links = metadata["links"] 311 | 312 | 313 | class Parameter: 314 | 315 | name = None 316 | description = None 317 | optional = None 318 | deprecated = None 319 | experimental = None 320 | default = None 321 | schema = {} 322 | 323 | def __init__(self): 324 | pass 325 | 326 | def from_metadata(self, metadata, version): 327 | 328 | if version.at_least("1.0.0"): 329 | if "name" in metadata: 330 | self.name = metadata["name"] 331 | if "description" in metadata: 332 | self.description = metadata["description"] 333 | if "optional" in metadata: 334 | self.optional = metadata["optional"] 335 | if "deprecated" in metadata: 336 | self.deprecated = metadata["deprecated"] 337 | if "experimental" in metadata: 338 | self.experimental = metadata["experimental"] 339 | if "default" in metadata: 340 | self.default = metadata["default"] 341 | if "schema" in metadata: 342 | self.schema = metadata["schema"] 343 | else: 344 | if "description" in metadata: 345 | self.description = metadata["description"] 346 | if "required" in metadata: 347 | self.optional = not metadata["required"] 348 | if "deprecated" in metadata: 349 | self.deprecated = metadata["deprecated"] 350 | if "experimental" in metadata: 351 | self.experimental = metadata["experimental"] 352 | if "schema" in metadata: 353 | self.schema = metadata["schema"] 354 | 355 | def get_type(self): 356 | 357 | if "subtype" in self.schema: 358 | return self.schema["subtype"] 359 | elif "type" in self.schema: 360 | if isinstance(self.schema["type"], list): 361 | return list_to_lang(self.schema["type"]) 362 | else: 363 | return self.schema["type"] 364 | elif isinstance(self.schema, list): 365 | type_list = [] 366 | for item in self.schema: 367 | if "subtype" in item: 368 | type_list.append(item["subtype"]) 369 | elif "type" in item: 370 | type_list.append(item["type"]) 371 | return list_to_lang(type_list) 372 | else: 373 | return self.schema["description"] 374 | 375 | 376 | # class ProcessInfo: 377 | # 378 | # id = None 379 | # 380 | # def __init__(self, id, parameters=None, desc=None, returns=None, returns_desc=None): 381 | # self.id = id 382 | # if parameters: 383 | # self.parameters = parameters 384 | # else: 385 | # self.parameters = [] 386 | # 387 | # self.desc = desc 388 | # self.returns = returns 389 | # self.returns_desc = returns_desc 390 | -------------------------------------------------------------------------------- /models/openeohub.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from distutils.version import LooseVersion 3 | from .models import HubJob 4 | 5 | HUB_URL = "https://hub.openeo.org" 6 | 7 | 8 | class HubBackend: 9 | 10 | name = None 11 | url = None 12 | version_urls = {} 13 | 14 | def __init__(self, url, name=None): 15 | self.url = url 16 | self.name = name 17 | self.version_urls = self.fetch_version_urls() 18 | 19 | def get_latest_version(self): 20 | latest_ver = "0.0.0" 21 | for ver, _ in self.version_urls.items(): 22 | if LooseVersion(latest_ver) <= LooseVersion(ver): 23 | latest_ver = ver 24 | return self.version_urls[latest_ver] 25 | 26 | def get_all_urls(self): 27 | return self.version_urls.values() 28 | 29 | def fetch_version_urls(self): 30 | ver_urls = {} 31 | try: 32 | backend_versions = requests.get(self.url, timeout=1) 33 | except: 34 | return ver_urls 35 | 36 | if backend_versions.status_code == 200: 37 | backend_versions = backend_versions.json() 38 | 39 | if "versions" in backend_versions: 40 | for bcknd in backend_versions["versions"]: 41 | if "api_version" in bcknd: 42 | try: 43 | if LooseVersion("0.4.0") <= LooseVersion(bcknd["api_version"]): 44 | if "url" in bcknd: 45 | ver_urls[bcknd["api_version"]] = str(bcknd["url"]) 46 | except: 47 | continue 48 | elif ".well-known" in str(self.url): 49 | for versions in backend_versions.values(): 50 | for version in versions: 51 | if "api_version" in version: 52 | try: 53 | if LooseVersion("0.4.0") <= LooseVersion(version["api_version"]): 54 | if "url" in version: 55 | ver_urls[version["api_version"]] = str(version["url"]) 56 | except: 57 | continue 58 | elif isinstance(self.url, dict): 59 | for ver, item in self.url.items(): 60 | ver_urls[ver] = str(item) 61 | else: 62 | ver_urls["0.0.0"] = self.url 63 | return ver_urls 64 | 65 | 66 | def get_hub_backends(): 67 | 68 | try: 69 | backendURL = requests.get('{}/api/backends'.format(HUB_URL), timeout=5) 70 | except: 71 | backendsALL = {} 72 | 73 | if backendURL.status_code == 200: 74 | backendsALL = backendURL.json() 75 | else: 76 | return [] 77 | # self.processgraphEdit.setText(json.dumps(self.backendsALL, indent=4)) 78 | 79 | backends = [] 80 | 81 | # Look for .well-known endpoint 82 | for name, url in backendsALL.items(): 83 | backends.append(HubBackend(url, name=name)) 84 | 85 | return backends 86 | 87 | 88 | def get_hub_jobs(): 89 | 90 | try: 91 | example_jobs_URL = requests.get('{}/api/process_graphs'.format(HUB_URL), timeout=5) 92 | except: 93 | example_jobs_URL = [] 94 | 95 | examples_job_list = example_jobs_URL.json() 96 | 97 | # Get names and process graphs of all available processes (7) 98 | example_jobs = [] 99 | 100 | for item in examples_job_list: 101 | job = HubJob(title=item['title'], process_graph=item['process_graph']) 102 | example_jobs.append(job) 103 | 104 | return example_jobs 105 | -------------------------------------------------------------------------------- /models/processgraph.py: -------------------------------------------------------------------------------- 1 | from .graphbuilder import GraphBuilder 2 | import collections 3 | 4 | class Processgraph: 5 | 6 | def __init__(self, node_id:str=None, builder:GraphBuilder=None): 7 | 8 | if not builder: 9 | builder = GraphBuilder() 10 | 11 | self.builder = builder 12 | self.node_id = node_id 13 | self.graph = builder.processes 14 | self.REDUCERS = ["min", "max", "mean", "median", "count"] 15 | 16 | def add_process(self, process_id, arguments): 17 | """ 18 | Returns a new Processgraph with the additional process (process_id) using the dictionary of arguments (args) 19 | Detects if the process is a reducer 20 | :param process_id: String, Process Id of the added process. 21 | :param args: Dict, Arguments of the process. 22 | :return: processgraph: Instance of the Processgraph class 23 | """ 24 | if process_id in self.REDUCERS: 25 | return self._reduce_time(process_id) 26 | else: 27 | return self.graph_add_process(process_id, arguments) 28 | 29 | def _reduce_time(self, reduce_function = "max"): 30 | """ 31 | Add a time reducer to the process graph, returns the process graph with the reducer 32 | :param reduce_function: String, Reducer function one of Processgraph.REDUCERS 33 | :return: processgraph: Instance of the Processgraph class 34 | """ 35 | process_id = 'reduce' 36 | 37 | args = { 38 | 'data': {'from_node': self.node_id}, 39 | 'dimension': 'temporal', 40 | 'reducer': { 41 | 'callback': { 42 | 'r1': { 43 | 'arguments': { 44 | 'data': { 45 | 'from_argument': 'data' 46 | } 47 | }, 48 | 'process_id': reduce_function, 49 | 'result': True 50 | } 51 | } 52 | } 53 | } 54 | 55 | return self.graph_add_process(process_id, args) 56 | 57 | def load_collection(self, arguments): 58 | """ 59 | Sets the collection of the processgraph 60 | id and a dictionary of arguments 61 | :param collection_id: String, Collection Id. 62 | """ 63 | self.builder = GraphBuilder() 64 | 65 | #collection_id = None 66 | #ex = None 67 | #tex = None 68 | #bands = None 69 | 70 | process_id = 'load_collection' 71 | 72 | #arguments = collections.OrderedDict({ 73 | # 'id': collection_id, 74 | # 'spatial_extent': ex, 75 | # 'temporal_extent': tex, 76 | # 'bands': bands 77 | #}) 78 | self.node_id = self.builder.process(process_id, arguments) 79 | self.graph = self.builder.processes 80 | 81 | 82 | def graph_add_process(self, process_id, args): 83 | """ 84 | Returns a new Processgraph with the additional process (process_id) using the dictionary of arguments (args) 85 | :param process_id: String, Process Id of the added process. 86 | :param args: Dict, Arguments of the process. 87 | :return: processgraph: Instance of the Processgraph class 88 | """ 89 | newbuilder = GraphBuilder(self.builder.processes) 90 | 91 | #args["from_node"] = self.node_id 92 | 93 | id = newbuilder.process(process_id, args) 94 | 95 | newProcessgraph = Processgraph(id, newbuilder) 96 | 97 | return newProcessgraph -------------------------------------------------------------------------------- /models/result.py: -------------------------------------------------------------------------------- 1 | from qgis.core import QgsRasterLayer, QgsCoordinateReferenceSystem 2 | from qgis.PyQt.QtCore import QFileInfo 3 | from qgis.core import QgsProject 4 | 5 | 6 | class Result(): 7 | 8 | def __init__(self, path=None, process_graph=None): 9 | self.path = path 10 | self.extent = None 11 | self.get_extent(process_graph) 12 | 13 | def display(self, layer_name=None): 14 | """ 15 | Displays an image from the given path on a new created QGIS Layer. 16 | """ 17 | # Check if string is provided 18 | if self.path: 19 | fileInfo = QFileInfo(self.path) 20 | path = fileInfo.filePath() 21 | baseName = fileInfo.baseName() 22 | if layer_name: 23 | layer = QgsRasterLayer(path, layer_name) 24 | else: 25 | layer = QgsRasterLayer(path, baseName) 26 | # reference layer correctly 27 | crs = QgsCoordinateReferenceSystem() 28 | crs.createFromId(4326, QgsCoordinateReferenceSystem.EpsgCrsId) 29 | if self.extent: 30 | if "crs" in self.extent: 31 | if isinstance(self.extent["crs"], str): 32 | self.extent["crs"] = self.extent["crs"].replace("EPSG:", "") 33 | crs.createFromId(int(self.extent["crs"])) 34 | else: 35 | crs.createFromId(self.extent["crs"]) 36 | 37 | layer.setCrs(crs) 38 | 39 | if not layer.isValid(): 40 | print("Layer failed to load!") 41 | return False 42 | 43 | QgsProject.instance().addMapLayer(layer) 44 | 45 | return True 46 | 47 | def get_extent(self, d): 48 | for k, v in d.items(): 49 | if k == "spatial_extent" or "k" == "extent": 50 | if v: 51 | if 'west' in v: 52 | self.extent = v 53 | return 54 | else: 55 | if isinstance(v, dict): 56 | self.get_extent(v) 57 | else: 58 | print("{0} : {1}".format(k, v)) -------------------------------------------------------------------------------- /openeo_connector.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | /*************************************************************************** 4 | OpenEO 5 | A QGIS plugin 6 | Plugin to access openEO compliant backends. 7 | Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ 8 | ------------------- 9 | begin : 2019-07-18 10 | git sha : $Format:%H$ 11 | copyright : (C) 2019 by Bernhard Goesswein 12 | email : bernhard.goesswein@geo.tuwien.ac.at 13 | ***************************************************************************/ 14 | /*************************************************************************** 15 | * * 16 | * This program is free software; you can redistribute it and/or modify * 17 | * it under the terms of the GNU General Public License as published by * 18 | * the Free Software Foundation; either version 2 of the License, or * 19 | * (at your option) any later version. * 20 | * * 21 | ***************************************************************************/ 22 | """ 23 | from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication 24 | from qgis.PyQt.QtGui import QIcon, QPixmap 25 | from qgis.PyQt.QtWidgets import QAction 26 | from PyQt5.QtGui import QIcon 27 | from qgis.PyQt import QtWidgets 28 | from PyQt5.QtCore import Qt 29 | 30 | # Initialize Qt resources from file resources.py 31 | from .resources import * 32 | # Import the code for the dialog 33 | from .login_dialog import LoginDialog 34 | from .openeo_connector_dialog import OpenEODialog 35 | import os.path 36 | os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" 37 | QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) # enable highdpi scaling 38 | QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) # use highdpi icons 39 | class OpenEO: 40 | """QGIS Plugin Implementation.""" 41 | 42 | def __init__(self, iface): 43 | """Constructor. 44 | :param iface: An interface instance that will be passed to this class 45 | which provides the hook by which you can manipulate the QGIS 46 | application at run time. 47 | :type iface: QgsInterface 48 | """ 49 | # Save reference to the QGIS interface 50 | self.iface = iface 51 | # initialize plugin directory 52 | self.plugin_dir = os.path.dirname(__file__) 53 | # initialize locale 54 | locale = QSettings().value('locale/userLocale')[0:2] 55 | locale_path = os.path.join( 56 | self.plugin_dir, 57 | 'i18n', 58 | 'OpenEO_{}.qm'.format(locale)) 59 | 60 | if os.path.exists(locale_path): 61 | self.translator = QTranslator() 62 | self.translator.load(locale_path) 63 | QCoreApplication.installTranslator(self.translator) 64 | 65 | # Declare instance attributes 66 | self.actions = [] 67 | self.menu = self.tr(u'&OpenEO ') 68 | 69 | # Check if plugin was started the first time in current QGIS session 70 | # Must be set in initGui() to survive plugin reloads 71 | self.first_start = None 72 | 73 | 74 | # noinspection PyMethodMayBeStatic 75 | def tr(self, message): 76 | """Get the translation for a string using Qt translation API. 77 | We implement this ourselves since we do not inherit QObject. 78 | :param message: String for translation. 79 | :type message: str, QString 80 | :returns: Translated version of message. 81 | :rtype: QString 82 | """ 83 | # noinspection PyTypeChecker,PyArgumentList,PyCallByClass 84 | return QCoreApplication.translate('OpenEO', message) 85 | 86 | 87 | def add_action( 88 | self, 89 | icon_path, 90 | text, 91 | callback, 92 | enabled_flag=True, 93 | add_to_menu=True, 94 | add_to_toolbar=True, 95 | status_tip=None, 96 | whats_this=None, 97 | parent=None): 98 | """Add a toolbar icon to the toolbar. 99 | :param icon_path: Path to the icon for this action. Can be a resource 100 | path (e.g. ':/plugins/foo/bar.png') or a normal file system path. 101 | :type icon_path: str 102 | :param text: Text that should be shown in menu items for this action. 103 | :type text: str 104 | :param callback: Function to be called when the action is triggered. 105 | :type callback: function 106 | :param enabled_flag: A flag indicating if the action should be enabled 107 | by default. Defaults to True. 108 | :type enabled_flag: bool 109 | :param add_to_menu: Flag indicating whether the action should also 110 | be added to the menu. Defaults to True. 111 | :type add_to_menu: bool 112 | :param add_to_toolbar: Flag indicating whether the action should also 113 | be added to the toolbar. Defaults to True. 114 | :type add_to_toolbar: bool 115 | :param status_tip: Optional text to show in a popup when mouse pointer 116 | hovers over the action. 117 | :type status_tip: str 118 | :param parent: Parent widget for the new action. Defaults None. 119 | :type parent: QWidget 120 | :param whats_this: Optional text to show in the status bar when the 121 | mouse pointer hovers over the action. 122 | :returns: The action that was created. Note that the action is also 123 | added to self.actions list. 124 | :rtype: QAction 125 | """ 126 | 127 | icon = QIcon(icon_path) 128 | action = QAction(icon, text, parent) 129 | action.triggered.connect(callback) 130 | action.setEnabled(enabled_flag) 131 | 132 | if status_tip is not None: 133 | action.setStatusTip(status_tip) 134 | 135 | if whats_this is not None: 136 | action.setWhatsThis(whats_this) 137 | 138 | if add_to_toolbar: 139 | # Adds plugin icon to Plugins toolbar 140 | self.iface.addToolBarIcon(action) 141 | 142 | if add_to_menu: 143 | self.iface.addPluginToMenu( 144 | self.menu, 145 | action) 146 | 147 | self.actions.append(action) 148 | 149 | return action 150 | 151 | def initGui(self): 152 | """Create the menu entries and toolbar icons inside the QGIS GUI.""" 153 | 154 | icon_path = os.path.join(os.path.dirname(__file__), 'images/icon_new.png') 155 | self.add_action( 156 | icon_path, 157 | text=self.tr(u'OpenEO Connector'), 158 | callback=self.run, 159 | parent=self.iface.mainWindow()) 160 | 161 | # will be set False in run() 162 | self.first_start = True 163 | 164 | def unload(self): 165 | """Removes the plugin menu item and icon from QGIS GUI.""" 166 | for action in self.actions: 167 | self.iface.removePluginMenu( 168 | self.tr(u'&OpenEO '), 169 | action) 170 | self.iface.removeToolBarIcon(action) 171 | 172 | def run(self): 173 | """Run method that performs all the real work""" 174 | 175 | # Create the dialog with elements (after translation) and keep reference 176 | # Only create GUI ONCE in callback, so that it will only load when the plugin is started 177 | if self.first_start: 178 | self.first_start = False 179 | self.dlg = LoginDialog(iface=self.iface, openeo=self) 180 | self.dlg.logo.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), 'images/icon_new.png'))) 181 | self.dlg.logo.setFixedSize(139, 89) 182 | 183 | # show the dialog 184 | self.dlg.show() 185 | # Run the dialog event loop 186 | result = self.dlg.exec_() 187 | # See if OK was pressed 188 | if result: 189 | # Do something useful here - delete the line containing pass and 190 | # substitute with your code. 191 | pass 192 | 193 | def login(self): 194 | """ 195 | Logs the user into the backend and starts the main openEO dialog, also closes this login dialog. 196 | """ 197 | backend = self.dlg.connect() 198 | 199 | if not backend: 200 | return 201 | 202 | self.dlg.close() 203 | self.dlg = OpenEODialog(interface=self.iface, backend=backend) 204 | self.dlg.infoBtn2.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 'images/info_icon.png'))) 205 | self.dlg.refreshButton.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 'images/reload_icon.png'))) 206 | self.dlg.refreshButton_service.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 'images/reload_icon.png'))) 207 | self.dlg.jobsManualBtn.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 208 | 'images/user_manual_icon.png'))) 209 | self.dlg.servicesManualBtn.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 210 | 'images/user_manual_icon.png'))) 211 | self.dlg.explorativeManualBtn.setIcon(QIcon(os.path.join(os.path.dirname(__file__), 212 | 'images/user_manual_icon.png'))) 213 | # self.dlg.setWindowFlags(Qt.WindowStaysOnTopHint) 214 | self.dlg.show() 215 | self.first_start = True 216 | # self.close() -------------------------------------------------------------------------------- /pb_tool.cfg: -------------------------------------------------------------------------------- 1 | #/*************************************************************************** 2 | # OpenEO 3 | # 4 | # Configuration file for plugin builder tool (pb_tool) 5 | # Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ 6 | # ------------------- 7 | # begin : 2019-07-18 8 | # copyright : (C) 2019 by Bernhard Goesswein 9 | # email : bernhard.goesswein@geo.tuwien.ac.at 10 | # ***************************************************************************/ 11 | # 12 | #/*************************************************************************** 13 | # * * 14 | # * This program is free software; you can redistribute it and/or modify * 15 | # * it under the terms of the GNU General Public License as published by * 16 | # * the Free Software Foundation; either version 2 of the License, or * 17 | # * (at your option) any later version. * 18 | # * * 19 | # ***************************************************************************/ 20 | # 21 | # 22 | # You can install pb_tool using: 23 | # pip install http://geoapt.net/files/pb_tool.zip 24 | # 25 | # Consider doing your development (and install of pb_tool) in a virtualenv. 26 | # 27 | # For details on setting up and using pb_tool, see: 28 | # http://g-sherman.github.io/plugin_build_tool/ 29 | # 30 | # Issues and pull requests here: 31 | # https://github.com/g-sherman/plugin_build_tool: 32 | # 33 | # Sane defaults for your plugin generated by the Plugin Builder are 34 | # already set below. 35 | # 36 | # As you add Python source files and UI files to your plugin, add 37 | # them to the appropriate [files] section below. 38 | 39 | [plugin] 40 | # Name of the plugin. This is the name of the directory that will 41 | # be created in .qgis2/python/plugins 42 | name: openeo_connector 43 | 44 | # Full path to where you want your plugin directory copied. If empty, 45 | # the QGIS default path will be used. Don't include the plugin name in 46 | # the path. 47 | plugin_path: 48 | 49 | [files] 50 | # Python files that should be deployed with the plugin 51 | python_files: __init__.py openeo_connector.py openeo_connector_dialog.py 52 | 53 | # The main dialog file that is loaded (not compiled) 54 | main_dialog: openeo_connector_dialog_base.ui 55 | 56 | # Other ui files for dialogs you create (these will be compiled) 57 | compiled_ui_files: 58 | 59 | # Resource file(s) that will be compiled 60 | resource_files: resources.qrc 61 | 62 | # Other files required for the plugin 63 | extras: metadata.txt icon.png 64 | 65 | # Other directories to be deployed with the plugin. 66 | # These must be subdirectories under the plugin directory 67 | extra_dirs: 68 | 69 | # ISO code(s) for any locales (translations), separated by spaces. 70 | # Corresponding .ts files must exist in the i18n directory 71 | locales: 72 | 73 | [help] 74 | # the built help directory that should be deployed with the plugin 75 | dir: help/build/html 76 | # the name of the directory to target in the deployed plugin 77 | target: help 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /plugin_upload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | """This script uploads a plugin package to the plugin repository. 4 | Authors: A. Pasotti, V. Picavet 5 | git sha : $TemplateVCSFormat 6 | """ 7 | 8 | import sys 9 | import getpass 10 | import xmlrpc.client 11 | from optparse import OptionParser 12 | 13 | standard_library.install_aliases() 14 | 15 | # Configuration 16 | PROTOCOL = 'https' 17 | SERVER = 'plugins.qgis.org' 18 | PORT = '443' 19 | ENDPOINT = '/plugins/RPC2/' 20 | VERBOSE = False 21 | 22 | 23 | def main(parameters, arguments): 24 | """Main entry point. 25 | 26 | :param parameters: Command line parameters. 27 | :param arguments: Command line arguments. 28 | """ 29 | address = "{protocol}://{username}:{password}@{server}:{port}{endpoint}".format( 30 | protocol=PROTOCOL, 31 | username=parameters.username, 32 | password=parameters.password, 33 | server=parameters.server, 34 | port=parameters.port, 35 | endpoint=ENDPOINT) 36 | print("Connecting to: %s" % hide_password(address)) 37 | 38 | server = xmlrpc.client.ServerProxy(address, verbose=VERBOSE) 39 | 40 | try: 41 | with open(arguments[0], 'rb') as handle: 42 | plugin_id, version_id = server.plugin.upload( 43 | xmlrpc.client.Binary(handle.read())) 44 | print("Plugin ID: %s" % plugin_id) 45 | print("Version ID: %s" % version_id) 46 | except xmlrpc.client.ProtocolError as err: 47 | print("A protocol error occurred") 48 | print("URL: %s" % hide_password(err.url, 0)) 49 | print("HTTP/HTTPS headers: %s" % err.headers) 50 | print("Error code: %d" % err.errcode) 51 | print("Error message: %s" % err.errmsg) 52 | except xmlrpc.client.Fault as err: 53 | print("A fault occurred") 54 | print("Fault code: %d" % err.faultCode) 55 | print("Fault string: %s" % err.faultString) 56 | 57 | 58 | def hide_password(url, start=6): 59 | """Returns the http url with password part replaced with '*'. 60 | 61 | :param url: URL to upload the plugin to. 62 | :type url: str 63 | 64 | :param start: Position of start of password. 65 | :type start: int 66 | """ 67 | start_position = url.find(':', start) + 1 68 | end_position = url.find('@') 69 | return "%s%s%s" % ( 70 | url[:start_position], 71 | '*' * (end_position - start_position), 72 | url[end_position:]) 73 | 74 | 75 | if __name__ == "__main__": 76 | parser = OptionParser(usage="%prog [options] plugin.zip") 77 | parser.add_option( 78 | "-w", "--password", dest="password", 79 | help="Password for plugin site", metavar="******") 80 | parser.add_option( 81 | "-u", "--username", dest="username", 82 | help="Username of plugin site", metavar="user") 83 | parser.add_option( 84 | "-p", "--port", dest="port", 85 | help="Server port to connect to", metavar="80") 86 | parser.add_option( 87 | "-s", "--server", dest="server", 88 | help="Specify server name", metavar="plugins.qgis.org") 89 | options, args = parser.parse_args() 90 | if len(args) != 1: 91 | print("Please specify zip file.\n") 92 | parser.print_help() 93 | sys.exit(1) 94 | if not options.server: 95 | options.server = SERVER 96 | if not options.port: 97 | options.port = PORT 98 | if not options.username: 99 | # interactive mode 100 | username = getpass.getuser() 101 | print("Please enter user name [%s] :" % username, end=' ') 102 | 103 | res = input() 104 | if res != "": 105 | options.username = res 106 | else: 107 | options.username = username 108 | if not options.password: 109 | # interactive mode 110 | options.password = getpass.getpass() 111 | main(options, args) 112 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | 25 | [MESSAGES CONTROL] 26 | 27 | # Enable the message, report, category or checker with the given id(s). You can 28 | # either give multiple identifier separated by comma (,) or put this option 29 | # multiple time. See also the "--disable" option for examples. 30 | #enable= 31 | 32 | # Disable the message, report, category or checker with the given id(s). You 33 | # can either give multiple identifiers separated by comma (,) or put this 34 | # option multiple times (only on the command line, not in the configuration 35 | # file where it should appear only once).You can also use "--disable=all" to 36 | # disable everything first and then reenable specific checks. For example, if 37 | # you want to run only the similarities checker, you can use "--disable=all 38 | # --enable=similarities". If you want to run only the classes checker, but have 39 | # no Warning level messages displayed, use"--disable=all --enable=classes 40 | # --disable=W" 41 | # see http://stackoverflow.com/questions/21487025/pylint-locally-defined-disables-still-give-warnings-how-to-suppress-them 42 | disable=locally-disabled,C0103 43 | 44 | 45 | [REPORTS] 46 | 47 | # Set the output format. Available formats are text, parseable, colorized, msvs 48 | # (visual studio) and html. You can also give a reporter class, eg 49 | # mypackage.mymodule.MyReporterClass. 50 | output-format=text 51 | 52 | # Put messages in a separate file for each module / package specified on the 53 | # command line instead of printing them on stdout. Reports (if any) will be 54 | # written in a file name "pylint_global.[txt|html]". 55 | files-output=no 56 | 57 | # Tells whether to display a full report or only the messages 58 | reports=yes 59 | 60 | # Python expression which should return a note less than 10 (10 is the highest 61 | # note). You have access to the variables errors warning, statement which 62 | # respectively contain the number of errors / warnings messages and the total 63 | # number of statements analyzed. This is used by the global evaluation report 64 | # (RP0004). 65 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 66 | 67 | # Add a comment according to your evaluation note. This is used by the global 68 | # evaluation report (RP0004). 69 | comment=no 70 | 71 | # Template used to display messages. This is a python new-style format string 72 | # used to format the message information. See doc for all details 73 | #msg-template= 74 | 75 | 76 | [BASIC] 77 | 78 | # Required attributes for module, separated by a comma 79 | required-attributes= 80 | 81 | # List of builtins function names that should not be used, separated by a comma 82 | bad-functions=map,filter,apply,input 83 | 84 | # Regular expression which should only match correct module names 85 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 86 | 87 | # Regular expression which should only match correct module level names 88 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 89 | 90 | # Regular expression which should only match correct class names 91 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 92 | 93 | # Regular expression which should only match correct function names 94 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 95 | 96 | # Regular expression which should only match correct method names 97 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 98 | 99 | # Regular expression which should only match correct instance attribute names 100 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 101 | 102 | # Regular expression which should only match correct argument names 103 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 104 | 105 | # Regular expression which should only match correct variable names 106 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 107 | 108 | # Regular expression which should only match correct attribute names in class 109 | # bodies 110 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 111 | 112 | # Regular expression which should only match correct list comprehension / 113 | # generator expression variable names 114 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 115 | 116 | # Good variable names which should always be accepted, separated by a comma 117 | good-names=i,j,k,ex,Run,_ 118 | 119 | # Bad variable names which should always be refused, separated by a comma 120 | bad-names=foo,bar,baz,toto,tutu,tata 121 | 122 | # Regular expression which should only match function or class names that do 123 | # not require a docstring. 124 | no-docstring-rgx=__.*__ 125 | 126 | # Minimum line length for functions/classes that require docstrings, shorter 127 | # ones are exempt. 128 | docstring-min-length=-1 129 | 130 | 131 | [MISCELLANEOUS] 132 | 133 | # List of note tags to take in consideration, separated by a comma. 134 | notes=FIXME,XXX,TODO 135 | 136 | 137 | [TYPECHECK] 138 | 139 | # Tells whether missing members accessed in mixin class should be ignored. A 140 | # mixin class is detected if its name ends with "mixin" (case insensitive). 141 | ignore-mixin-members=yes 142 | 143 | # List of classes names for which member attributes should not be checked 144 | # (useful for classes with attributes dynamically set). 145 | ignored-classes=SQLObject 146 | 147 | # When zope mode is activated, add a predefined set of Zope acquired attributes 148 | # to generated-members. 149 | zope=no 150 | 151 | # List of members which are set dynamically and missed by pylint inference 152 | # system, and so shouldn't trigger E0201 when accessed. Python regular 153 | # expressions are accepted. 154 | generated-members=REQUEST,acl_users,aq_parent 155 | 156 | 157 | [VARIABLES] 158 | 159 | # Tells whether we should check for unused import in __init__ files. 160 | init-import=no 161 | 162 | # A regular expression matching the beginning of the name of dummy variables 163 | # (i.e. not used). 164 | dummy-variables-rgx=_$|dummy 165 | 166 | # List of additional names supposed to be defined in builtins. Remember that 167 | # you should avoid to define new builtins when possible. 168 | additional-builtins= 169 | 170 | 171 | [FORMAT] 172 | 173 | # Maximum number of characters on a single line. 174 | max-line-length=80 175 | 176 | # Regexp for a line that is allowed to be longer than the limit. 177 | ignore-long-lines=^\s*(# )??$ 178 | 179 | # Allow the body of an if to be on the same line as the test if there is no 180 | # else. 181 | single-line-if-stmt=no 182 | 183 | # List of optional constructs for which whitespace checking is disabled 184 | no-space-check=trailing-comma,dict-separator 185 | 186 | # Maximum number of lines in a module 187 | max-module-lines=1000 188 | 189 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 190 | # tab). 191 | indent-string=' ' 192 | 193 | 194 | [SIMILARITIES] 195 | 196 | # Minimum lines number of a similarity. 197 | min-similarity-lines=4 198 | 199 | # Ignore comments when computing similarities. 200 | ignore-comments=yes 201 | 202 | # Ignore docstrings when computing similarities. 203 | ignore-docstrings=yes 204 | 205 | # Ignore imports when computing similarities. 206 | ignore-imports=no 207 | 208 | 209 | [IMPORTS] 210 | 211 | # Deprecated modules which should not be used, separated by a comma 212 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 213 | 214 | # Create a graph of every (i.e. internal and external) dependencies in the 215 | # given file (report RP0402 must not be disabled) 216 | import-graph= 217 | 218 | # Create a graph of external dependencies in the given file (report RP0402 must 219 | # not be disabled) 220 | ext-import-graph= 221 | 222 | # Create a graph of internal dependencies in the given file (report RP0402 must 223 | # not be disabled) 224 | int-import-graph= 225 | 226 | 227 | [DESIGN] 228 | 229 | # Maximum number of arguments for function / method 230 | max-args=5 231 | 232 | # Argument names that match this expression will be ignored. Default to name 233 | # with leading underscore 234 | ignored-argument-names=_.* 235 | 236 | # Maximum number of locals for function / method body 237 | max-locals=15 238 | 239 | # Maximum number of return / yield for function / method body 240 | max-returns=6 241 | 242 | # Maximum number of branch for function / method body 243 | max-branches=12 244 | 245 | # Maximum number of statements in function / method body 246 | max-statements=50 247 | 248 | # Maximum number of parents for a class (see R0901). 249 | max-parents=7 250 | 251 | # Maximum number of attributes for a class (see R0902). 252 | max-attributes=7 253 | 254 | # Minimum number of public methods for a class (see R0903). 255 | min-public-methods=2 256 | 257 | # Maximum number of public methods for a class (see R0904). 258 | max-public-methods=20 259 | 260 | 261 | [CLASSES] 262 | 263 | # List of interface methods to ignore, separated by a comma. This is used for 264 | # instance to not check methods defines in Zope's Interface base class. 265 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 266 | 267 | # List of method names used to declare (i.e. assign) instance attributes. 268 | defining-attr-methods=__init__,__new__,setUp 269 | 270 | # List of valid names for the first argument in a class method. 271 | valid-classmethod-first-arg=cls 272 | 273 | # List of valid names for the first argument in a metaclass class method. 274 | valid-metaclass-classmethod-first-arg=mcs 275 | 276 | 277 | [EXCEPTIONS] 278 | 279 | # Exceptions that will emit a warning when being caught. Defaults to 280 | # "Exception" 281 | overgeneral-exceptions=Exception 282 | -------------------------------------------------------------------------------- /resources.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Resource object code 4 | # 5 | # Created by: The Resource Compiler for PyQt5 (Qt v5.6.2) 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore 10 | 11 | qt_resource_data = b"\ 12 | \x00\x00\x04\x0a\ 13 | \x89\ 14 | \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ 15 | \x00\x00\x17\x00\x00\x00\x18\x08\x06\x00\x00\x00\x11\x7c\x66\x75\ 16 | \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ 17 | \x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ 18 | \x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ 19 | \x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd9\x02\x15\ 20 | \x16\x11\x2c\x9d\x48\x83\xbb\x00\x00\x03\x8a\x49\x44\x41\x54\x48\ 21 | \xc7\xad\x95\x4b\x68\x5c\x55\x18\xc7\x7f\xe7\xdc\x7b\x67\xe6\xce\ 22 | \x4c\x66\x26\x49\xd3\x24\x26\xa6\xc6\xf8\x40\x21\xa5\x04\xb3\x28\ 23 | \xda\x98\x20\xa5\x0b\xad\x55\xa8\x2b\xc5\x50\x1f\xa0\x6e\x34\x2b\ 24 | \x45\x30\x14\x02\xba\x52\x69\x15\x17\x66\x63\x45\x97\x95\xa0\xad\ 25 | \x0b\xfb\xc0\x06\x25\xb6\x71\x61\x12\x41\x50\xdb\x2a\x21\xd1\xe2\ 26 | \x24\xf3\x9e\xc9\xcc\xbd\xe7\x1c\x17\x35\x43\x1e\x33\x21\xb6\xfd\ 27 | \x56\x87\xf3\x9d\xfb\xfb\x1e\xf7\xff\x9d\x23\x8c\x31\x43\x95\xf4\ 28 | \x85\x1e\x3f\x3b\x35\xac\xfd\xcc\x43\xdc\xa4\x49\x3b\xfe\x9d\x1d\ 29 | \xdb\x7b\x22\x90\x78\xf8\xb2\x28\xa7\xbe\x7d\xc1\x4b\x9d\x79\xdf\ 30 | \x18\x15\xe5\x16\x99\x10\x56\xde\x69\xdc\x3f\x22\xfd\xec\xd4\xf0\ 31 | \xad\x04\x03\x18\xa3\xa2\x7e\x76\x6a\x58\xde\x68\x2b\xb4\x36\xf8\ 32 | \xbe\xc6\x18\x53\xdb\xef\xe7\xfa\xec\xed\x67\x63\x10\x42\x00\xf0\ 33 | \xfb\xd5\x65\x2a\x15\x45\xc7\x6d\x0d\x00\xc4\xa2\xc1\xaa\x6f\x0d\ 34 | \x3e\x6c\xab\xc2\x1c\x56\xa4\x77\x4b\xb0\xf2\x35\x15\x5f\x21\x85\ 35 | \xe0\xc8\x6b\x5f\x92\x2d\x37\x33\x39\xf9\x03\x27\x8e\x1f\xa2\xf7\ 36 | \xbe\x9d\x04\x1c\x0b\x37\xe4\xac\xff\xa6\x30\x87\xbd\xba\x00\x6a\ 37 | \x06\x79\xe5\xf5\xaf\x89\xd9\x92\xc5\xcc\x0a\xd9\x7c\x19\xcf\xe9\ 38 | \xe2\xe4\xa9\x2f\x78\x7c\xff\x01\x72\x85\x0a\x2b\x65\x1f\xa5\x4c\ 39 | \xb5\xb2\x55\x16\x80\xbd\x31\xda\xda\x20\x1f\x7d\x3e\xcd\xc2\xfd\ 40 | \x59\xa6\x93\x39\x92\xd1\x22\xea\x9b\x16\xce\x9d\x3f\xce\xe0\x83\ 41 | \x03\x24\x82\x59\x3a\xdb\x7b\x88\xc7\x82\x68\x63\x58\xc9\xcc\x62\ 42 | \x8c\x21\x18\xb0\x6a\xc3\x37\x06\x49\x16\xff\x24\x6b\xa5\x49\xbb\ 43 | \x25\xbc\xa2\xa6\x21\xbb\x40\x7f\xdf\x00\x83\xbd\x01\x8e\x3c\xd5\ 44 | \x45\xd7\x8e\x6b\x9c\x9c\x98\x25\x1a\xb6\xe8\xbe\x3d\xc2\xdd\x77\ 45 | \x44\x48\xc4\x1c\x22\xe1\xeb\x58\x59\xaf\xcf\xd3\x33\x29\x2e\x34\ 46 | \x2d\x91\x93\x3e\xbe\x34\x78\x01\xc5\xe2\x61\xc5\xae\x72\x8e\x70\ 47 | \xc8\xc2\x0d\x5a\xbc\xf5\xee\x2f\x9c\xfa\x3e\x86\x69\x7a\x8e\xcf\ 48 | \x26\xe6\xf9\x63\xa1\x44\xa1\xa4\xd0\xda\x6c\x0d\x2f\x15\x7c\xb4\ 49 | \x67\x28\x59\x0a\xcf\xd6\x54\xe2\x06\x13\x87\x2b\x6f\x68\xa6\x27\ 50 | \xaf\x31\x32\x36\xc7\xb2\x7f\x17\xef\x7d\x7c\x8c\x33\x67\xcf\x12\ 51 | \x70\x24\x4a\x69\xd6\x6a\x46\xd6\xd3\x70\x72\xa9\x82\x67\x34\x45\ 52 | \xad\x28\xdb\x1a\x15\x34\x98\xff\x46\xed\xef\x37\x0d\x99\xbf\x4a\ 53 | \x3c\x30\x38\xc0\xc8\x4b\xaf\x92\x5a\x9c\xe2\xe0\x23\x6d\x74\xb4\ 54 | \xba\x84\x5d\x0b\x29\x45\x7d\xb8\x94\x82\x96\xb6\x10\xf3\xc5\x12\ 55 | \x2a\xef\x53\x11\x1a\x63\xad\x3f\x93\x19\x85\xf1\xb1\x77\x58\x5a\ 56 | \xf8\x99\x97\x9f\xe9\xa6\x75\x47\x90\xc6\xb8\x43\xd8\xb5\xb6\xce\ 57 | \xfc\xfa\xfd\x00\xfb\x3e\xf4\xc8\x05\x35\xba\x5e\xeb\x46\x21\xf9\ 58 | \xcf\x0a\xa9\x8c\x87\xe3\x48\xdc\x90\xb5\x6e\x98\x6a\xaa\x65\xf2\ 59 | \x52\x92\x43\x2f\x5e\xc2\x8c\x02\x1a\x10\xf5\x07\xac\xc3\x75\x70\ 60 | \x83\x92\x80\xb3\xf9\xd0\x26\xf8\x8f\xb3\x29\xc6\x3e\xb8\x8c\x19\ 61 | \x35\x75\x6b\x7b\x7e\x3c\xca\x45\x0c\x7e\x49\x31\xf4\x58\x3b\xf7\ 62 | \xf6\x34\x90\x88\x39\x04\x1c\x59\x1f\xfe\xdb\xd5\x3c\x5f\x9d\x4b\ 63 | \x32\xfd\x44\xb2\xba\xd7\xfa\xb6\x60\xcf\xde\x16\xdc\x90\x45\x4c\ 64 | \x4a\x2a\x9e\x62\xfe\x4e\xc5\xc8\xc1\x4e\xda\x76\x86\xe8\xe9\x0a\ 65 | \xe3\xd8\x92\x58\xd4\xc6\xb2\x44\x6d\x78\x2a\x53\xe1\xca\x7c\x99\ 66 | \x63\x5d\xbf\x56\x9d\xbd\x9f\x44\x18\x7a\xba\x95\x27\x0f\xb4\xd3\ 67 | \xdc\x18\xc0\xf3\x0d\x52\x40\xd8\xb5\xb0\xa4\x20\x14\xb2\x70\x6c\ 68 | \x81\x63\xcb\xaa\x42\xd6\xfd\xb7\xf4\xec\xa3\x06\xa0\x50\x52\xd8\ 69 | \x4e\x1b\x7e\x4a\xd3\x31\xf9\x29\xcf\xfe\xd4\x49\x7f\x5f\x13\xfb\ 70 | \xfa\x9b\x71\x43\x92\x58\xd4\x21\x18\x90\xac\xde\xb0\x42\x50\x13\ 71 | \x58\x33\xf3\x88\x6b\xa1\xfd\x65\x96\xf2\x79\xc6\x43\x7b\xd8\x75\ 72 | \x38\xcc\x3d\xdd\xd1\xaa\xcf\x71\xe4\xff\x7f\x91\x56\x33\xaf\xea\ 73 | \x37\xe7\xa1\x94\x21\x16\xb5\xd1\x06\x2c\x29\x36\xf5\x72\x9b\x96\ 74 | \x95\xc0\xc4\xda\x9d\x78\x83\x43\x53\x22\x80\x65\x09\x1c\xfb\x86\ 75 | \xc1\x00\xe7\x25\x70\x14\x48\x6f\x1e\x22\x51\xe3\x75\xd9\xb6\xa5\ 76 | \x81\xa3\x32\xb1\xfb\xf4\x0c\x30\xb8\xb1\x82\x9b\xb0\x09\x60\x30\ 77 | \xb1\xfb\xf4\xcc\xbf\xa0\xe9\x6e\xae\x5a\xdf\x4b\x81\x00\x00\x00\ 78 | \x00\x49\x45\x4e\x44\xae\x42\x60\x82\ 79 | " 80 | 81 | qt_resource_name = b"\ 82 | \x00\x07\ 83 | \x07\x3b\xe0\xb3\ 84 | \x00\x70\ 85 | \x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\ 86 | \x00\x10\ 87 | \x0c\xdc\x84\xa2\ 88 | \x00\x6f\ 89 | \x00\x70\x00\x65\x00\x6e\x00\x65\x00\x6f\x00\x5f\x00\x63\x00\x6f\x00\x6e\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x6f\x00\x72\ 90 | \x00\x08\ 91 | \x0a\x61\x5a\xa7\ 92 | \x00\x69\ 93 | \x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ 94 | " 95 | 96 | qt_resource_struct = b"\ 97 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ 98 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ 99 | \x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ 100 | \x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ 101 | " 102 | 103 | def qInitResources(): 104 | QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) 105 | 106 | def qCleanupResources(): 107 | QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) 108 | 109 | qInitResources() 110 | -------------------------------------------------------------------------------- /resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icon_new.png 4 | info_icon.png 5 | reload_icon.png 6 | 7 | 8 | -------------------------------------------------------------------------------- /scripts/compile-strings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | LRELEASE=$1 3 | LOCALES=$2 4 | 5 | 6 | for LOCALE in ${LOCALES} 7 | do 8 | echo "Processing: ${LOCALE}.ts" 9 | # Note we don't use pylupdate with qt .pro file approach as it is flakey 10 | # about what is made available. 11 | $LRELEASE i18n/${LOCALE}.ts 12 | done 13 | -------------------------------------------------------------------------------- /scripts/run-env-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | QGIS_PREFIX_PATH=/usr/local/qgis-2.0 4 | if [ -n "$1" ]; then 5 | QGIS_PREFIX_PATH=$1 6 | fi 7 | 8 | echo ${QGIS_PREFIX_PATH} 9 | 10 | 11 | export QGIS_PREFIX_PATH=${QGIS_PREFIX_PATH} 12 | export QGIS_PATH=${QGIS_PREFIX_PATH} 13 | export LD_LIBRARY_PATH=${QGIS_PREFIX_PATH}/lib 14 | export PYTHONPATH=${QGIS_PREFIX_PATH}/share/qgis/python:${QGIS_PREFIX_PATH}/share/qgis/python/plugins:${PYTHONPATH} 15 | 16 | echo "QGIS PATH: $QGIS_PREFIX_PATH" 17 | export QGIS_DEBUG=0 18 | export QGIS_LOG_FILE=/tmp/inasafe/realtime/logs/qgis.log 19 | 20 | export PATH=${QGIS_PREFIX_PATH}/bin:$PATH 21 | 22 | echo "This script is intended to be sourced to set up your shell to" 23 | echo "use a QGIS 2.0 built in $QGIS_PREFIX_PATH" 24 | echo 25 | echo "To use it do:" 26 | echo "source $BASH_SOURCE /your/optional/install/path" 27 | echo 28 | echo "Then use the make file supplied here e.g. make guitest" 29 | -------------------------------------------------------------------------------- /scripts/update-strings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | LOCALES=$* 3 | 4 | # Get newest .py files so we don't update strings unnecessarily 5 | 6 | CHANGED_FILES=0 7 | PYTHON_FILES=`find . -regex ".*\(ui\|py\)$" -type f` 8 | for PYTHON_FILE in $PYTHON_FILES 9 | do 10 | CHANGED=$(stat -c %Y $PYTHON_FILE) 11 | if [ ${CHANGED} -gt ${CHANGED_FILES} ] 12 | then 13 | CHANGED_FILES=${CHANGED} 14 | fi 15 | done 16 | 17 | # Qt translation stuff 18 | # for .ts file 19 | UPDATE=false 20 | for LOCALE in ${LOCALES} 21 | do 22 | TRANSLATION_FILE="i18n/$LOCALE.ts" 23 | if [ ! -f ${TRANSLATION_FILE} ] 24 | then 25 | # Force translation string collection as we have a new language file 26 | touch ${TRANSLATION_FILE} 27 | UPDATE=true 28 | break 29 | fi 30 | 31 | MODIFICATION_TIME=$(stat -c %Y ${TRANSLATION_FILE}) 32 | if [ ${CHANGED_FILES} -gt ${MODIFICATION_TIME} ] 33 | then 34 | # Force translation string collection as a .py file has been updated 35 | UPDATE=true 36 | break 37 | fi 38 | done 39 | 40 | if [ ${UPDATE} == true ] 41 | # retrieve all python files 42 | then 43 | echo ${PYTHON_FILES} 44 | # update .ts 45 | echo "Please provide translations by editing the translation files below:" 46 | for LOCALE in ${LOCALES} 47 | do 48 | echo "i18n/"${LOCALE}".ts" 49 | # Note we don't use pylupdate with qt .pro file approach as it is flakey 50 | # about what is made available. 51 | pylupdate4 -noobsolete ${PYTHON_FILES} -ts i18n/${LOCALE}.ts 52 | done 53 | else 54 | echo "No need to edit any translation files (.ts) because no python files" 55 | echo "has been updated since the last update translation. " 56 | fi 57 | -------------------------------------------------------------------------------- /service_create_dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | /*************************************************************************** 4 | JobAdaptDialog 5 | 6 | This class is responsible for adapting existing openEO jobs. 7 | 8 | author : 2020 by Bernhard Goesswein 9 | email : bernhard.goesswein@geo.tuwien.ac.at 10 | ***************************************************************************/ 11 | """ 12 | import os 13 | import json 14 | from copy import deepcopy 15 | import ast 16 | from qgis.PyQt import uic 17 | 18 | from PyQt5 import QtWidgets, QtCore, QtGui 19 | 20 | from PyQt5.QtWidgets import QApplication, QComboBox, QMessageBox, QDialog, QHBoxLayout, QTextEdit 21 | from qgis.PyQt.QtWidgets import QTableWidgetItem, QPushButton, QApplication 22 | 23 | from PyQt5.QtGui import QIcon, QFont 24 | from PyQt5.QtCore import Qt, QSize, QDate 25 | from .utils.logging import warning, error 26 | 27 | from .spatial_dialog import SpatialDialog 28 | from .temp_dialog import TempDialog 29 | from .band_dialog import BandDialog 30 | ######################################################################################################################## 31 | ######################################################################################################################## 32 | 33 | # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer 34 | FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'service_create_dialog.ui')) 35 | 36 | 37 | class ServiceCreateDialog(QtWidgets.QDialog, FORM_CLASS): 38 | """ 39 | This class is responsible for adapting existing openEO jobs. 40 | """ 41 | def __init__(self, parent=None, iface=None, backend=None): 42 | """ 43 | Constructor method: Initializing the button behaviours and the Table entries. 44 | :param parent: parent dialog of this dialog (e.g. OpenEODialog). 45 | :param iface: Interface to show the dialog. 46 | :param job: models.Job: Job that should be adapted 47 | :param backend: Backend: Currently connected backend. 48 | :param subgraph: dict: Usually None, but if the JobAdaptDialog is called for a subgraph 49 | (e.g. when using a reducer), it contains the process graph to be edited. 50 | :param row: Int: row of the process graph table widget related to the process to edit. 51 | :param main_dia: JobAdaptDialog: Contains the parent JobAdaptDialog object, if a subgraph is given. 52 | """ 53 | super(ServiceCreateDialog, self).__init__(parent) 54 | 55 | QApplication.setStyle("cleanlooks") 56 | self.setupUi(self) 57 | 58 | self.iface = iface 59 | self.backend = backend 60 | 61 | self.cur_job = None 62 | 63 | self.jobs = backend.get_jobs() 64 | if not isinstance(self.jobs, list): 65 | warning(self.iface, "Error loading Jobs from the backend (Response status code not 200)") 66 | self.jobs = [] 67 | 68 | self.processgraph_buffer = {} 69 | 70 | # Raw graph 71 | self.rawgraphBtn.clicked.connect(self.raw_graph) 72 | self.sendButton.clicked.connect(self.send_service) 73 | 74 | self.jobs_to_table() 75 | 76 | self.cancelButton.clicked.connect(self.close) 77 | 78 | self.typesComboBox.addItem("Select a Type") 79 | self.service_types = self.backend.get_service_types() 80 | for key, val in self.service_types.items(): 81 | self.typesComboBox.addItem(key) 82 | 83 | self.rawgraph_window = None 84 | 85 | def raw_graph(self): 86 | """ 87 | Shows the raw process graph window, to copy paste graphs. 88 | """ 89 | if not self.processgraph_buffer: 90 | warning(self.iface, "No Job selected !") 91 | return 92 | 93 | self.rawgraph_window = QDialog(parent=self) 94 | hbox = QHBoxLayout() 95 | self.raw_pg_box = QTextEdit() 96 | self.raw_pg_box.setText(json.dumps({"process_graph": self.processgraph_buffer}, indent=4)) 97 | self.raw_pg_box.setReadOnly(False) 98 | hbox.addWidget(self.raw_pg_box) 99 | 100 | # close_btn = QPushButton('Close') 101 | # hbox.addWidget(close_btn) 102 | # close_btn.clicked.connect(self.rawgraph_window.close) 103 | 104 | self.rawgraph_window.setMinimumHeight(600) 105 | self.rawgraph_window.setMinimumWidth(400) 106 | self.rawgraph_window.setLayout(hbox) 107 | self.rawgraph_window.setWindowTitle('Service Information') 108 | self.rawgraph_window.show() 109 | 110 | def get_job_by_id(self, job_id): 111 | for job in self.jobs: 112 | if job.id == job_id: 113 | return job 114 | 115 | return None 116 | 117 | def send_service(self): 118 | """ 119 | Sends the currently defined process graph as a new service to the backend. 120 | """ 121 | s_type = self.typesComboBox.currentText() 122 | 123 | if s_type == "Select a Type": 124 | error(self.iface, "Type must be selected!") 125 | return 126 | 127 | if not self.processgraph_buffer: 128 | error(self.iface, "No job selected!") 129 | return 130 | 131 | # warning(self.iface, str(self.processgraph_buffer)) 132 | 133 | service_status = self.backend.service_create(process=self.processgraph_buffer, s_type=s_type, 134 | title=self.titleText.text(), description=self.descriptionText.text()) 135 | if service_status: 136 | error(self.iface, "Service creation failed: {}".format(str(self.backend.error_msg_from_resp(service_status)))) 137 | else: 138 | self.close() 139 | 140 | def mark_row(self, row): 141 | """ 142 | Marks a row / process on the process graph table visually. 143 | :param row: int: Row number of the process on the process graph table. 144 | """ 145 | for pr_row in range(self.jobsTableWidget.rowCount()): 146 | for pr_col in range(self.jobsTableWidget.columnCount()): 147 | if self.jobsTableWidget.item(pr_row, pr_col): 148 | if pr_row == row: 149 | self.jobsTableWidget.item(pr_row, pr_col).setBackground(Qt.lightGray) 150 | else: 151 | self.jobsTableWidget.item(pr_row, pr_col).setBackground(Qt.white) 152 | 153 | def init_jobs_table(self): 154 | """ 155 | Initializes the process graph table by setting the column settings and headers. 156 | """ 157 | self.jobsTableWidget.clear() 158 | self.jobsTableWidget.setColumnCount(3) 159 | self.jobsTableWidget.setHorizontalHeaderLabels(['Title', 'Created', 'Selection']) 160 | header = self.jobsTableWidget.horizontalHeader() 161 | self.jobsTableWidget.setSortingEnabled(True) 162 | self.jobsTableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) 163 | header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) 164 | header.setSectionResizeMode(1, QtWidgets.QHeaderView.Interactive) 165 | header.setSectionResizeMode(2, QtWidgets.QHeaderView.Interactive) 166 | self.jobsTableWidget.setSelectionBehavior(QtWidgets.QTableView.SelectRows) 167 | 168 | def select_job(self, job_id, row): 169 | 170 | self.mark_row(row) 171 | 172 | job = self.backend.detailed_job(job_id) 173 | 174 | if job: 175 | self.processgraph_buffer = job.process.process_graph 176 | else: 177 | self.processgraph_buffer = {} 178 | 179 | def jobs_to_table(self): 180 | """ 181 | Loads the current jobs to the jobs table. 182 | """ 183 | self.init_jobs_table() 184 | 185 | suc_jobs = [] 186 | 187 | for job in self.jobs: 188 | if job.status == "finished": 189 | suc_jobs.append(job) 190 | 191 | self.jobsTableWidget.setRowCount(len(suc_jobs)) 192 | 193 | row = 0 194 | for job in suc_jobs: 195 | if job.status == "error": 196 | continue 197 | 198 | if not job.title: 199 | qitem = QTableWidgetItem("Untitled Job!") 200 | qitem.setFlags(QtCore.Qt.ItemIsEnabled) 201 | self.jobsTableWidget.setItem(row, 0, qitem) 202 | else: 203 | qitem = QTableWidgetItem(job.title) 204 | qitem.setFlags(QtCore.Qt.ItemIsEnabled) 205 | self.jobsTableWidget.setItem(row, 0, qitem) 206 | 207 | if job.created: 208 | qitem = QTableWidgetItem(job.created.strftime("%Y-%m-%d_%H-%M-%S")) 209 | qitem.setFlags(QtCore.Qt.ItemIsEnabled) 210 | self.jobsTableWidget.setItem(row, 1, qitem) 211 | 212 | sel_btn = QPushButton(self.jobsTableWidget) 213 | sel_btn.setText("Select") 214 | self.jobsTableWidget.setCellWidget(row, 2, sel_btn) 215 | sel_btn.clicked.connect(lambda *args, job_id=job.id, sel_row=row: self.select_job(job_id, sel_row)) 216 | row += 1 217 | 218 | self.jobsTableWidget.setSortingEnabled(True) 219 | self.jobsTableWidget.resizeColumnsToContents() 220 | -------------------------------------------------------------------------------- /service_create_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | TemporalDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 547 10 | 418 11 | 12 | 13 | 14 | Qt::NoFocus 15 | 16 | 17 | Create Service from Job 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | true 26 | 27 | 28 | 29 | true 30 | 31 | 32 | 33 | <html><head/><body><p><span style=" font-weight:600;">Title</span></p></body></html> 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | true 44 | 45 | 46 | 47 | true 48 | 49 | 50 | 51 | <html><head/><body><p><span style=" font-weight:600;">Description</span></p><p><br/></p></body></html> 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 0 60 | 0 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | true 69 | 70 | 71 | 72 | 75 73 | true 74 | true 75 | 76 | 77 | 78 | <html><head/><body><p>Service Type</p></body></html> 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | true 89 | 90 | 91 | 92 | true 93 | 94 | 95 | 96 | <html><head/><body><p><span style=" font-weight:600;">Jobs</span></p></body></html> 97 | 98 | 99 | 100 | 101 | 102 | 103 | QAbstractItemView::SelectRows 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Raw Graph 115 | 116 | 117 | 118 | 119 | 120 | 121 | Create Service 122 | 123 | 124 | 125 | 126 | 127 | 128 | Cancel 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /spatial_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SpatialDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 410 10 | 288 11 | 12 | 13 | 14 | Set Spatial Extent 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <html><head/><body><p><span style=" font-weight:600;">Set Spatial Extent</span></p></body></html> 23 | 24 | 25 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ../../../../../../../../../../../../../../../../.designer/backup/reload_icon.png../../../../../../../../../../../../../../../../.designer/backup/reload_icon.png 47 | 48 | 49 | 50 | 51 | 52 | 53 | Get Extent 54 | 55 | 56 | 57 | 58 | 59 | 60 | Draw Extent 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Qt::Horizontal 73 | 74 | 75 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | buttonBox 85 | accepted() 86 | SpatialDialog 87 | accept() 88 | 89 | 90 | 248 91 | 254 92 | 93 | 94 | 157 95 | 274 96 | 97 | 98 | 99 | 100 | buttonBox 101 | rejected() 102 | SpatialDialog 103 | reject() 104 | 105 | 106 | 316 107 | 260 108 | 109 | 110 | 286 111 | 274 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /temp_dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | /*************************************************************************** 4 | TempDialog 5 | 6 | This class is responsible for choosing temporal extent for a openEO job. 7 | 8 | author : 2020 by Bernhard Goesswein 9 | email : bernhard.goesswein@geo.tuwien.ac.at 10 | ***************************************************************************/ 11 | """ 12 | import os 13 | 14 | from qgis.PyQt import uic 15 | 16 | from .utils.logging import warning 17 | 18 | from PyQt5 import QtWidgets 19 | 20 | from PyQt5.QtWidgets import QApplication 21 | from PyQt5.QtCore import QDate 22 | 23 | ######################################################################################################################## 24 | ######################################################################################################################## 25 | 26 | # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer 27 | FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'temp_dialog.ui')) 28 | 29 | 30 | class TempDialog(QtWidgets.QDialog, FORM_CLASS): 31 | """ 32 | This class is responsible for choosing temporal extent for a openEO job. 33 | """ 34 | def __init__(self, parent=None, iface=None, extent=None, col_max=None, col_min=None): 35 | """ 36 | Constructor method: Initializing the button behaviours and the Table entries. 37 | :param parent: parent dialog of this dialog (e.g. OpenEODialog). 38 | :param iface: Interface to show the dialog. 39 | :param extent: dict: Current extent of the job. 40 | """ 41 | super(TempDialog, self).__init__(parent) 42 | 43 | QApplication.setStyle("cleanlooks") 44 | 45 | self.iface = iface 46 | self.called = False 47 | self.called2 = False 48 | self.processes = None 49 | 50 | # 2014-10-03T00:00:00Z 51 | 52 | self.col_max = col_max 53 | self.col_min = col_min 54 | 55 | self.setupUi(self) 56 | 57 | if extent: 58 | self.extent = extent 59 | else: 60 | end_date = QDate.currentDate() 61 | start_date = QDate.currentDate() 62 | if col_max: 63 | end_date = col_max 64 | if col_min: 65 | start_date = col_min 66 | self.extent = [start_date, end_date] 67 | 68 | self.date_selection(self.extent) 69 | 70 | self.init_calendars() 71 | 72 | self.buttonBox.accepted.connect(self.accept_dialog) 73 | 74 | self.start_date_extension = None 75 | self.end_date_extension = None 76 | 77 | def accept_dialog(self): 78 | """ 79 | Dialog is finished and the chosen temporal extent gets sent to the parent (main) adaption dialog. 80 | """ 81 | if self.EndDateEdit.date() < self.StartDateEdit.date(): 82 | warning(self.iface, "End date is placed before the start date!") 83 | self.parent().receive_temporal_extent(extent=[self.show_start(), self.show_end()]) 84 | 85 | def date_selection(self, dates): 86 | """ 87 | Initializes the date selection by setting the current dates. 88 | :param dates: list: List of two date strings, first the start date and second the 89 | end date of the temporal extent. 90 | """ 91 | if dates: 92 | start_date = dates[0] 93 | end_date = dates[1] 94 | 95 | if isinstance(start_date, str): 96 | if "T" in start_date: 97 | start_date = dates[0].split("T")[0] 98 | self.start_date_extension = "T"+dates[0].split("T")[1] 99 | else: 100 | self.start_date_extension = "" 101 | self.StartDateEdit.setDate(QDate.fromString(start_date, "yyyy-MM-dd")) 102 | else: 103 | self.start_date_extension = "" 104 | self.StartDateEdit.setDate(start_date) 105 | 106 | if isinstance(end_date, str): 107 | if "T" in end_date: 108 | end_date = dates[1].split("T")[0] 109 | self.end_date_extension = "T"+dates[1].split("T")[1] 110 | else: 111 | self.end_date_extension = "" 112 | self.EndDateEdit.setDate(QDate.fromString(end_date, "yyyy-MM-dd")) 113 | else: 114 | self.end_date_extension = "" 115 | self.EndDateEdit.setDate(end_date) 116 | 117 | def init_calendars(self): 118 | """ 119 | Initializes the calendar 120 | """ 121 | if self.col_max: 122 | self.endCalendarWidget.setMaximumDate(self.col_max) 123 | self.EndDateEdit.setMaximumDate(self.col_max) 124 | self.startCalendarWidget.setMaximumDate(self.col_max) 125 | self.StartDateEdit.setMaximumDate(self.col_max) 126 | else: 127 | self.endCalendarWidget.setMaximumDate(QDate.currentDate()) 128 | self.EndDateEdit.setMaximumDate(QDate.currentDate()) 129 | self.startCalendarWidget.setMaximumDate(QDate.currentDate()) 130 | self.StartDateEdit.setMaximumDate(QDate.currentDate()) 131 | 132 | if self.col_min: 133 | self.startCalendarWidget.setMinimumDate(self.col_min) 134 | self.StartDateEdit.setMinimumDate(self.col_min) 135 | self.endCalendarWidget.setMinimumDate(self.col_min) 136 | self.EndDateEdit.setMinimumDate(self.col_min) 137 | 138 | self.startCalendarWidget.setSelectedDate(self.StartDateEdit.date()) 139 | self.endCalendarWidget.setSelectedDate(self.EndDateEdit.date()) 140 | 141 | self.startCalendarWidget.clicked[QDate].connect(self.pick_start) 142 | self.endCalendarWidget.clicked[QDate].connect(self.pick_end) 143 | self.StartDateEdit.dateChanged.connect(self.update_cal_start) 144 | self.EndDateEdit.dateChanged.connect(self.update_cal_end) 145 | 146 | def update_cal_start(self): 147 | """ 148 | Updates calendar by start date widget. 149 | """ 150 | start = self.StartDateEdit.date() 151 | self.startCalendarWidget.setSelectedDate(start) 152 | 153 | def update_cal_end(self): 154 | """ 155 | Updates calendar by end date widget. 156 | """ 157 | end = self.EndDateEdit.date() 158 | self.endCalendarWidget.setSelectedDate(end) 159 | 160 | def pick_start(self): 161 | """ 162 | Starts picking the date 163 | """ 164 | fe = self.endCalendarWidget.selectedDate() 165 | fs = self.startCalendarWidget.selectedDate() 166 | if fs > fe: 167 | warning(self.iface, "Start date needs to be before the end date!") 168 | fs = fe 169 | self.startCalendarWidget.setSelectedDate(fs) 170 | 171 | self.StartDateEdit.setDate(fs) 172 | 173 | def pick_end(self): 174 | """ 175 | Ends picking the date 176 | """ 177 | fe = self.endCalendarWidget.selectedDate() 178 | fs = self.startCalendarWidget.selectedDate() 179 | if fe < fs: 180 | warning(self.iface, "End date needs to be after the start date!") 181 | fe = fs 182 | self.endCalendarWidget.setSelectedDate(fe) 183 | 184 | self.EndDateEdit.setDate(fe) 185 | 186 | def show_start(self): 187 | """ 188 | Returns the start date correctly formatted. 189 | """ 190 | if self.StartDateEdit.dateChanged: 191 | start = self.StartDateEdit.date() 192 | if not self.start_date_extension: 193 | self.start_date_extension = "" 194 | return start.toString("yyyy-MM-dd")+self.start_date_extension 195 | 196 | def show_end(self): 197 | """ 198 | Returns the end date correctly formatted. 199 | """ 200 | if self.EndDateEdit.dateChanged: 201 | end = self.EndDateEdit.date() 202 | if not self.end_date_extension: 203 | self.end_date_extension = "" 204 | return end.toString("yyyy-MM-dd")+self.end_date_extension 205 | -------------------------------------------------------------------------------- /temp_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | TemporalDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 806 10 | 356 11 | 12 | 13 | 14 | Qt::NoFocus 15 | 16 | 17 | Set Temporal Extent 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | false 26 | 27 | 28 | <html><head/><body><p><span style=" font-weight:600;">Start Date</span></p></body></html> 29 | 30 | 31 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | false 42 | 43 | 44 | yyyy-MM-dd 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | false 56 | 57 | 58 | <html><head/><body><p><span style=" font-weight:600;">End Date</span></p></body></html> 59 | 60 | 61 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | yyyy-MM-dd 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | Qt::Horizontal 81 | 82 | 83 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | buttonBox 93 | accepted() 94 | TemporalDialog 95 | accept() 96 | 97 | 98 | 248 99 | 254 100 | 101 | 102 | 157 103 | 274 104 | 105 | 106 | 107 | 108 | buttonBox 109 | rejected() 110 | TemporalDialog 111 | reject() 112 | 113 | 114 | 316 115 | 260 116 | 117 | 118 | 286 119 | 274 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | # import qgis libs so that ve set the correct sip api version 2 | import qgis # pylint: disable=W0611 # NOQA -------------------------------------------------------------------------------- /test/qgis_interface.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """QGIS plugin implementation. 3 | 4 | .. note:: This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | .. note:: This source code was copied from the 'postgis viewer' application 10 | with original authors: 11 | Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk 12 | Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org 13 | Copyright (c) 2014 Tim Sutton, tim@linfiniti.com 14 | 15 | """ 16 | 17 | __author__ = 'tim@linfiniti.com' 18 | __revision__ = '$Format:%H$' 19 | __date__ = '10/01/2011' 20 | __copyright__ = ( 21 | 'Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and ' 22 | 'Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org' 23 | 'Copyright (c) 2014 Tim Sutton, tim@linfiniti.com' 24 | ) 25 | 26 | import logging 27 | from qgis.PyQt.QtCore import QObject, pyqtSlot, pyqtSignal 28 | from qgis.core import QgsMapLayerRegistry 29 | from qgis.gui import QgsMapCanvasLayer 30 | LOGGER = logging.getLogger('QGIS') 31 | 32 | 33 | #noinspection PyMethodMayBeStatic,PyPep8Naming 34 | class QgisInterface(QObject): 35 | """Class to expose QGIS objects and functions to plugins. 36 | 37 | This class is here for enabling us to run unit tests only, 38 | so most methods are simply stubs. 39 | """ 40 | currentLayerChanged = pyqtSignal(QgsMapCanvasLayer) 41 | 42 | def __init__(self, canvas): 43 | """Constructor 44 | :param canvas: 45 | """ 46 | QObject.__init__(self) 47 | self.canvas = canvas 48 | # Set up slots so we can mimic the behaviour of QGIS when layers 49 | # are added. 50 | LOGGER.debug('Initialising canvas...') 51 | # noinspection PyArgumentList 52 | QgsMapLayerRegistry.instance().layersAdded.connect 53 | # noinspection PyArgumentList 54 | QgsMapLayerRegistry.instance().layerWasAdded.connect 55 | # noinspection PyArgumentList 56 | QgsMapLayerRegistry.instance().removeAll.connect 57 | 58 | # For processing module 59 | self.destCrs = None 60 | 61 | @pyqtSlot('QStringList') 62 | def addLayers(self, layers): 63 | """Handle layers being added to the registry so they show up in canvas. 64 | 65 | :param layers: list list of map layers that were added 66 | 67 | .. note:: The QgsInterface api does not include this method, 68 | it is added here as a helper to facilitate testing. 69 | """ 70 | #LOGGER.debug('addLayers called on qgis_interface') 71 | #LOGGER.debug('Number of layers being added: %s' % len(layers)) 72 | #LOGGER.debug('Layer Count Before: %s' % len(self.canvas.layers())) 73 | current_layers = self.canvas.layers() 74 | final_layers = [] 75 | for layer in current_layers: 76 | final_layers.append(QgsMapCanvasLayer(layer)) 77 | for layer in layers: 78 | final_layers.append(QgsMapCanvasLayer(layer)) 79 | 80 | self.canvas.setLayerSet(final_layers) 81 | #LOGGER.debug('Layer Count After: %s' % len(self.canvas.layers())) 82 | 83 | @pyqtSlot('QgsMapLayer') 84 | def addLayer(self, layer): 85 | """Handle a layer being added to the registry so it shows up in canvas. 86 | 87 | :param layer: list list of map layers that were added 88 | 89 | .. note: The QgsInterface api does not include this method, it is added 90 | here as a helper to facilitate testing. 91 | 92 | .. note: The addLayer method was deprecated in QGIS 1.8 so you should 93 | not need this method much. 94 | """ 95 | pass 96 | 97 | @pyqtSlot() 98 | def removeAllLayers(self): 99 | """Remove layers from the canvas before they get deleted.""" 100 | self.canvas.setLayerSet([]) 101 | 102 | def newProject(self): 103 | """Create new project.""" 104 | # noinspection PyArgumentList 105 | QgsMapLayerRegistry.instance().removeAllMapLayers() 106 | 107 | # ---------------- API Mock for QgsInterface follows ------------------- 108 | 109 | def zoomFull(self): 110 | """Zoom to the map full extent.""" 111 | pass 112 | 113 | def zoomToPrevious(self): 114 | """Zoom to previous view extent.""" 115 | pass 116 | 117 | def zoomToNext(self): 118 | """Zoom to next view extent.""" 119 | pass 120 | 121 | def zoomToActiveLayer(self): 122 | """Zoom to extent of active layer.""" 123 | pass 124 | 125 | def addVectorLayer(self, path, base_name, provider_key): 126 | """Add a vector layer. 127 | 128 | :param path: Path to layer. 129 | :type path: str 130 | 131 | :param base_name: Base name for layer. 132 | :type base_name: str 133 | 134 | :param provider_key: Provider key e.g. 'ogr' 135 | :type provider_key: str 136 | """ 137 | pass 138 | 139 | def addRasterLayer(self, path, base_name): 140 | """Add a raster layer given a raster layer file name 141 | 142 | :param path: Path to layer. 143 | :type path: str 144 | 145 | :param base_name: Base name for layer. 146 | :type base_name: str 147 | """ 148 | pass 149 | 150 | def activeLayer(self): 151 | """Get pointer to the active layer (layer selected in the legend).""" 152 | # noinspection PyArgumentList 153 | layers = QgsMapLayerRegistry.instance().mapLayers() 154 | for item in layers: 155 | return layers[item] 156 | 157 | def addToolBarIcon(self, action): 158 | """Add an icon to the plugins toolbar. 159 | 160 | :param action: Action to add to the toolbar. 161 | :type action: QAction 162 | """ 163 | pass 164 | 165 | def removeToolBarIcon(self, action): 166 | """Remove an action (icon) from the plugin toolbar. 167 | 168 | :param action: Action to add to the toolbar. 169 | :type action: QAction 170 | """ 171 | pass 172 | 173 | def addToolBar(self, name): 174 | """Add toolbar with specified name. 175 | 176 | :param name: Name for the toolbar. 177 | :type name: str 178 | """ 179 | pass 180 | 181 | def mapCanvas(self): 182 | """Return a pointer to the map canvas.""" 183 | return self.canvas 184 | 185 | def mainWindow(self): 186 | """Return a pointer to the main window. 187 | 188 | In case of QGIS it returns an instance of QgisApp. 189 | """ 190 | pass 191 | 192 | def addDockWidget(self, area, dock_widget): 193 | """Add a dock widget to the main window. 194 | 195 | :param area: Where in the ui the dock should be placed. 196 | :type area: 197 | 198 | :param dock_widget: A dock widget to add to the UI. 199 | :type dock_widget: QDockWidget 200 | """ 201 | pass 202 | 203 | def legendInterface(self): 204 | """Get the legend.""" 205 | return self.canvas 206 | -------------------------------------------------------------------------------- /test/tenbytenraster.asc: -------------------------------------------------------------------------------- 1 | NCOLS 10 2 | NROWS 10 3 | XLLCENTER 1535380.000000 4 | YLLCENTER 5083260.000000 5 | DX 10 6 | DY 10 7 | NODATA_VALUE -9999 8 | 0 1 2 3 4 5 6 7 8 9 9 | 0 1 2 3 4 5 6 7 8 9 10 | 0 1 2 3 4 5 6 7 8 9 11 | 0 1 2 3 4 5 6 7 8 9 12 | 0 1 2 3 4 5 6 7 8 9 13 | 0 1 2 3 4 5 6 7 8 9 14 | 0 1 2 3 4 5 6 7 8 9 15 | 0 1 2 3 4 5 6 7 8 9 16 | 0 1 2 3 4 5 6 7 8 9 17 | 0 1 2 3 4 5 6 7 8 9 18 | CRS 19 | NOTES 20 | -------------------------------------------------------------------------------- /test/tenbytenraster.asc.aux.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Point 4 | 5 | 6 | 7 | 9 8 | 4.5 9 | 0 10 | 2.872281323269 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/tenbytenraster.keywords: -------------------------------------------------------------------------------- 1 | title: Tenbytenraster 2 | -------------------------------------------------------------------------------- /test/tenbytenraster.lic: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tim Sutton, Linfiniti Consulting CC 5 | 6 | 7 | 8 | tenbytenraster.asc 9 | 2700044251 10 | Yes 11 | Tim Sutton 12 | Tim Sutton (QGIS Source Tree) 13 | Tim Sutton 14 | This data is publicly available from QGIS Source Tree. The original 15 | file was created and contributed to QGIS by Tim Sutton. 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/tenbytenraster.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /test/tenbytenraster.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 0 26 | 27 | -------------------------------------------------------------------------------- /test/test_init.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Tests QGIS plugin init.""" 3 | 4 | __author__ = 'Tim Sutton ' 5 | __revision__ = '$Format:%H$' 6 | __date__ = '17/10/2010' 7 | __license__ = "GPL" 8 | __copyright__ = 'Copyright 2012, Australia Indonesia Facility for ' 9 | __copyright__ += 'Disaster Reduction' 10 | 11 | import os 12 | import unittest 13 | import logging 14 | import configparser 15 | 16 | LOGGER = logging.getLogger('QGIS') 17 | 18 | 19 | class TestInit(unittest.TestCase): 20 | """Test that the plugin init is usable for QGIS. 21 | 22 | Based heavily on the validator class by Alessandro 23 | Passoti available here: 24 | 25 | http://github.com/qgis/qgis-django/blob/master/qgis-app/ 26 | plugins/validator.py 27 | 28 | """ 29 | 30 | def test_read_init(self): 31 | """Test that the plugin __init__ will validate on plugins.qgis.org.""" 32 | 33 | # You should update this list according to the latest in 34 | # https://github.com/qgis/qgis-django/blob/master/qgis-app/ 35 | # plugins/validator.py 36 | 37 | required_metadata = [ 38 | 'name', 39 | 'description', 40 | 'version', 41 | 'qgisMinimumVersion', 42 | 'email', 43 | 'author'] 44 | 45 | file_path = os.path.abspath(os.path.join( 46 | os.path.dirname(__file__), os.pardir, 47 | 'metadata.txt')) 48 | LOGGER.info(file_path) 49 | metadata = [] 50 | parser = configparser.ConfigParser() 51 | parser.optionxform = str 52 | parser.read(file_path) 53 | message = 'Cannot find a section named "general" in %s' % file_path 54 | assert parser.has_section('general'), message 55 | metadata.extend(parser.items('general')) 56 | 57 | for expectation in required_metadata: 58 | message = ('Cannot find metadata "%s" in metadata source (%s).' % ( 59 | expectation, file_path)) 60 | 61 | self.assertIn(expectation, dict(metadata), message) 62 | 63 | if __name__ == '__main__': 64 | unittest.main() 65 | -------------------------------------------------------------------------------- /test/test_openeo_connector_dialog.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Dialog test. 3 | 4 | .. note:: This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | """ 10 | 11 | __author__ = 'bernhard.goesswein@geo.tuwien.ac.at' 12 | __date__ = '2019-07-18' 13 | __copyright__ = 'Copyright 2019, Bernhard Goesswein' 14 | 15 | import unittest 16 | 17 | from qgis.PyQt.QtGui import QDialogButtonBox, QDialog 18 | 19 | from openeo_connector_dialog import OpenEODialog 20 | 21 | from utilities import get_qgis_app 22 | QGIS_APP = get_qgis_app() 23 | 24 | 25 | class OpenEODialogTest(unittest.TestCase): 26 | """Test dialog works.""" 27 | 28 | def setUp(self): 29 | """Runs before each test.""" 30 | self.dialog = OpenEODialog(None) 31 | 32 | def tearDown(self): 33 | """Runs after each test.""" 34 | self.dialog = None 35 | 36 | def test_dialog_ok(self): 37 | """Test we can click OK.""" 38 | 39 | button = self.dialog.button_box.button(QDialogButtonBox.Ok) 40 | button.click() 41 | result = self.dialog.result() 42 | self.assertEqual(result, QDialog.Accepted) 43 | 44 | def test_dialog_cancel(self): 45 | """Test we can click cancel.""" 46 | button = self.dialog.button_box.button(QDialogButtonBox.Cancel) 47 | button.click() 48 | result = self.dialog.result() 49 | self.assertEqual(result, QDialog.Rejected) 50 | 51 | if __name__ == "__main__": 52 | suite = unittest.makeSuite(OpenEODialogTest) 53 | runner = unittest.TextTestRunner(verbosity=2) 54 | runner.run(suite) 55 | 56 | -------------------------------------------------------------------------------- /test/test_qgis_environment.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Tests for QGIS functionality. 3 | 4 | 5 | .. note:: This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | """ 11 | __author__ = 'tim@linfiniti.com' 12 | __date__ = '20/01/2011' 13 | __copyright__ = ('Copyright 2012, Australia Indonesia Facility for ' 14 | 'Disaster Reduction') 15 | 16 | import os 17 | import unittest 18 | from qgis.core import ( 19 | QgsProviderRegistry, 20 | QgsCoordinateReferenceSystem, 21 | QgsRasterLayer) 22 | 23 | from .utilities import get_qgis_app 24 | QGIS_APP = get_qgis_app() 25 | 26 | 27 | class QGISTest(unittest.TestCase): 28 | """Test the QGIS Environment""" 29 | 30 | def test_qgis_environment(self): 31 | """QGIS environment has the expected providers""" 32 | 33 | r = QgsProviderRegistry.instance() 34 | self.assertIn('gdal', r.providerList()) 35 | self.assertIn('ogr', r.providerList()) 36 | self.assertIn('postgres', r.providerList()) 37 | 38 | def test_projection(self): 39 | """Test that QGIS properly parses a wkt string. 40 | """ 41 | crs = QgsCoordinateReferenceSystem() 42 | wkt = ( 43 | 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",' 44 | 'SPHEROID["WGS_1984",6378137.0,298.257223563]],' 45 | 'PRIMEM["Greenwich",0.0],UNIT["Degree",' 46 | '0.0174532925199433]]') 47 | crs.createFromWkt(wkt) 48 | auth_id = crs.authid() 49 | expected_auth_id = 'EPSG:4326' 50 | self.assertEqual(auth_id, expected_auth_id) 51 | 52 | # now test for a loaded layer 53 | path = os.path.join(os.path.dirname(__file__), 'tenbytenraster.asc') 54 | title = 'TestRaster' 55 | layer = QgsRasterLayer(path, title) 56 | auth_id = layer.crs().authid() 57 | self.assertEqual(auth_id, expected_auth_id) 58 | 59 | if __name__ == '__main__': 60 | unittest.main() 61 | -------------------------------------------------------------------------------- /test/test_resources.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Resources test. 3 | 4 | .. note:: This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | """ 10 | 11 | __author__ = 'bernhard.goesswein@geo.tuwien.ac.at' 12 | __date__ = '2019-07-18' 13 | __copyright__ = 'Copyright 2019, Bernhard Goesswein' 14 | 15 | import unittest 16 | 17 | from qgis.PyQt.QtGui import QIcon 18 | 19 | 20 | 21 | class OpenEODialogTest(unittest.TestCase): 22 | """Test rerources work.""" 23 | 24 | def setUp(self): 25 | """Runs before each test.""" 26 | pass 27 | 28 | def tearDown(self): 29 | """Runs after each test.""" 30 | pass 31 | 32 | def test_icon_png(self): 33 | """Test we can click OK.""" 34 | path = ':/plugins/OpenEO/icon.png' 35 | icon = QIcon(path) 36 | self.assertFalse(icon.isNull()) 37 | 38 | if __name__ == "__main__": 39 | suite = unittest.makeSuite(OpenEOResourcesTest) 40 | runner = unittest.TextTestRunner(verbosity=2) 41 | runner.run(suite) 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /test/test_translations.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Safe Translations Test. 3 | 4 | .. note:: This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | """ 10 | from .utilities import get_qgis_app 11 | 12 | __author__ = 'ismailsunni@yahoo.co.id' 13 | __date__ = '12/10/2011' 14 | __copyright__ = ('Copyright 2012, Australia Indonesia Facility for ' 15 | 'Disaster Reduction') 16 | import unittest 17 | import os 18 | 19 | from qgis.PyQt.QtCore import QCoreApplication, QTranslator 20 | 21 | QGIS_APP = get_qgis_app() 22 | 23 | 24 | class SafeTranslationsTest(unittest.TestCase): 25 | """Test translations work.""" 26 | 27 | def setUp(self): 28 | """Runs before each test.""" 29 | if 'LANG' in iter(os.environ.keys()): 30 | os.environ.__delitem__('LANG') 31 | 32 | def tearDown(self): 33 | """Runs after each test.""" 34 | if 'LANG' in iter(os.environ.keys()): 35 | os.environ.__delitem__('LANG') 36 | 37 | def test_qgis_translations(self): 38 | """Test that translations work.""" 39 | parent_path = os.path.join(__file__, os.path.pardir, os.path.pardir) 40 | dir_path = os.path.abspath(parent_path) 41 | file_path = os.path.join( 42 | dir_path, 'i18n', 'af.qm') 43 | translator = QTranslator() 44 | translator.load(file_path) 45 | QCoreApplication.installTranslator(translator) 46 | 47 | expected_message = 'Goeie more' 48 | real_message = QCoreApplication.translate("@default", 'Good morning') 49 | self.assertEqual(real_message, expected_message) 50 | 51 | 52 | if __name__ == "__main__": 53 | suite = unittest.makeSuite(SafeTranslationsTest) 54 | runner = unittest.TextTestRunner(verbosity=2) 55 | runner.run(suite) 56 | -------------------------------------------------------------------------------- /test/utilities.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Common functionality used by regression tests.""" 3 | 4 | import sys 5 | import logging 6 | 7 | 8 | LOGGER = logging.getLogger('QGIS') 9 | QGIS_APP = None # Static variable used to hold hand to running QGIS app 10 | CANVAS = None 11 | PARENT = None 12 | IFACE = None 13 | 14 | 15 | def get_qgis_app(): 16 | """ Start one QGIS application to test against. 17 | 18 | :returns: Handle to QGIS app, canvas, iface and parent. If there are any 19 | errors the tuple members will be returned as None. 20 | :rtype: (QgsApplication, CANVAS, IFACE, PARENT) 21 | 22 | If QGIS is already running the handle to that app will be returned. 23 | """ 24 | 25 | try: 26 | from qgis.PyQt import QtGui, QtCore 27 | from qgis.core import QgsApplication 28 | from qgis.gui import QgsMapCanvas 29 | from .qgis_interface import QgisInterface 30 | except ImportError: 31 | return None, None, None, None 32 | 33 | global QGIS_APP # pylint: disable=W0603 34 | 35 | if QGIS_APP is None: 36 | gui_flag = True # All test will run qgis in gui mode 37 | #noinspection PyPep8Naming 38 | QGIS_APP = QgsApplication(sys.argv, gui_flag) 39 | # Make sure QGIS_PREFIX_PATH is set in your env if needed! 40 | QGIS_APP.initQgis() 41 | s = QGIS_APP.showSettings() 42 | LOGGER.debug(s) 43 | 44 | global PARENT # pylint: disable=W0603 45 | if PARENT is None: 46 | #noinspection PyPep8Naming 47 | PARENT = QtGui.QWidget() 48 | 49 | global CANVAS # pylint: disable=W0603 50 | if CANVAS is None: 51 | #noinspection PyPep8Naming 52 | CANVAS = QgsMapCanvas(PARENT) 53 | CANVAS.resize(QtCore.QSize(400, 400)) 54 | 55 | global IFACE # pylint: disable=W0603 56 | if IFACE is None: 57 | # QgisInterface is a stub implementation of the QGIS plugin interface 58 | #noinspection PyPep8Naming 59 | IFACE = QgisInterface(CANVAS) 60 | 61 | return QGIS_APP, CANVAS, IFACE, PARENT 62 | -------------------------------------------------------------------------------- /user_manual_text.txt: -------------------------------------------------------------------------------- 1 | 2 | 2. On the right hand side of the Figure above, you can see the Button, which transfers you to the User Manual. 3 | Furthermore, you are enabled to switch between the four tabs - “Backend Info”, “Adapt Job”, “Jobs” and “Services”. 4 | 5 | How to explore the Backend capabilities? 6 | 7 | 3. The "Backend Info" tab provides information about the backend, in the "Backend Description" there is the official 8 | description of the backend. The "Available Collections" let you switch between the available (input) data collections 9 | of the backend, whereas the text field below provides a description of the selected collection. The "Supported Processes" 10 | let you see the supported processes of the backend. By choosing a process in the Combobox, a information button shows you 11 | a description about the process and the table below shows you the possible arguments and their type as well as 12 | an example if provided. 13 | 14 | How to prepare a Process Graph? 15 | 16 | 4. In the "Adapt Job" tab you can load existing jobs, either from the openEO hub or by already existing jobs of your user 17 | at the backend. It will then be displayed in the centered Textfield. If you want to create a complete new Job you can do 18 | so, by clicking on "Create New Job in Web Editor", then your web browser will open up with the openEO web editor. If you 19 | have loaded or copied a process graph in the Textfield you can adapt the extent of the input data used for the job by 20 | clicking on the "Adapt xxx" buttons, there you have build in QGIS features to conviniently change the extent. 21 | When finishing the process graph, you can click on "Create Job/Service" to send the process graph to the backend and 22 | create the job remotely (Note that you have to start the processing manually in the "Jobs"). 23 | 24 | How to work with existing Jobs? 25 | 26 | 5. In the "Jobs" tab you see all jobs with their status that you have currently under the logged in user at the backend. 27 | If the status is "submitted" you need to execute it to be able to download the result. If the status is "error" you can 28 | view the error message in the "Error" tab. The Information tab provides meta information about the job execution. 29 | The process graph shows you the graph used to execute the job, you can also load the process graph of an existing 30 | Job by clicking on "Copy and Adapt Job Process Graph in QGIS Plugin" in the bottom of the process graph window. 31 | 32 | How to work with existing Services? 33 | 34 | 6. The "Services" tab works exactly like the "Job" tab. You have all your services listed up. The difference between 35 | services and jobs in openEO is, that services will provide you the data of the current QGIS view, so you can change the 36 | extent of the QGIS view and see the data from there. Note that this is currently in an experimental phase and might cause 37 | time out errors, so you might zoom in to have a small QGIS view extent to prevent this. 38 | 39 | -------------------------------------------------------------------------------- /utils/logging.py: -------------------------------------------------------------------------------- 1 | from qgis.core import QgsMessageLog, Qgis 2 | 3 | 4 | def debug(message): 5 | QgsMessageLog.logMessage(message, level=Qgis.Info) 6 | 7 | 8 | def info(iface, message, duration=5): 9 | QgsMessageLog.logMessage(message, level=Qgis.Info) 10 | iface.messageBar().pushMessage( 11 | "Info", 12 | message, 13 | level=Qgis.Info, 14 | duration=duration 15 | ) 16 | 17 | 18 | def warning(iface, message, duration=5): 19 | QgsMessageLog.logMessage(message, level=Qgis.Warning) 20 | iface.messageBar().pushMessage( 21 | "Warning", 22 | message, 23 | level=Qgis.Warning, 24 | duration=duration 25 | ) 26 | 27 | 28 | def error(iface, message, duration=5): 29 | QgsMessageLog.logMessage(message, level=Qgis.Critical) 30 | iface.messageBar().pushMessage( 31 | "Error", 32 | message, 33 | level=Qgis.Critical, 34 | duration=duration 35 | ) 36 | --------------------------------------------------------------------------------