From 421d82056a516532e32bf8a722c79fb93a2feb13 Mon Sep 17 00:00:00 2001 From: Markus Metz Date: Tue, 27 Aug 2019 17:43:00 +0200 Subject: [PATCH 01/12] GRASS addons fix typo --- GRASS_addons/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GRASS_addons/README.md b/GRASS_addons/README.md index fd899f4..eb13279 100644 --- a/GRASS_addons/README.md +++ b/GRASS_addons/README.md @@ -2,7 +2,7 @@ GRASS addons for the GIS dissolve tool ====================================== Here are GRASS addons that are the base for QGIS plugins. The QGIS -plugins will use exactly the same fuunctionality and workflow, but with +plugins will use exactly the same functionality and workflow, but with a different user interface. ## How to install a GRASS addon From f08e328971ca66a083d32ece71f74b53f481950d Mon Sep 17 00:00:00 2001 From: Markus Metz Date: Wed, 28 Aug 2019 14:26:43 +0200 Subject: [PATCH 02/12] ecmwf_addregions refining, more robust GRASS interface --- QGIS_plugin/ecmwf_dissolve_tool/Makefile | 2 +- QGIS_plugin/ecmwf_dissolve_tool/__init__.py | 2 +- .../ecmwf_dissolve_tool.py | 20 +++++++++++++------ .../ecmwf_dissolve_tool_dialog.py | 2 +- .../ecmwf_dissolve_tool_dialog_base.ui | 2 +- QGIS_plugin/ecmwf_dissolve_tool/metadata.txt | 6 +++--- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/QGIS_plugin/ecmwf_dissolve_tool/Makefile b/QGIS_plugin/ecmwf_dissolve_tool/Makefile index 771ce91..37a3e1f 100644 --- a/QGIS_plugin/ecmwf_dissolve_tool/Makefile +++ b/QGIS_plugin/ecmwf_dissolve_tool/Makefile @@ -1,7 +1,7 @@ #/*************************************************************************** # DissolveTool # -# This plugin updates ECMWF partner regions and river basins +# This plugin updates ECMWF partner regions # ------------------- # begin : 2019-08-26 # git sha : $Format:%H$ diff --git a/QGIS_plugin/ecmwf_dissolve_tool/__init__.py b/QGIS_plugin/ecmwf_dissolve_tool/__init__.py index e767faa..ddae281 100644 --- a/QGIS_plugin/ecmwf_dissolve_tool/__init__.py +++ b/QGIS_plugin/ecmwf_dissolve_tool/__init__.py @@ -3,7 +3,7 @@ /*************************************************************************** DissolveTool A QGIS plugin - This plugin updates ECMWF partner regions and river basins + This plugin updates ECMWF partner regions Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ ------------------- begin : 2019-08-26 diff --git a/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool.py b/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool.py index 9ea2552..d28bdf2 100644 --- a/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool.py +++ b/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool.py @@ -3,7 +3,7 @@ /*************************************************************************** DissolveTool A QGIS plugin - This plugin updates ECMWF partner regions and river basins + This plugin updates ECMWF partner regions Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ ------------------- begin : 2019-08-26 @@ -64,7 +64,7 @@ def __init__(self, iface): # Declare instance attributes self.actions = [] - self.menu = self.tr(u'&ECMWF dissolve tool') + self.menu = self.tr(u'&ECMWF') # Check if plugin was started the first time in current QGIS session # Must be set in initGui() to survive plugin reloads @@ -166,7 +166,7 @@ def initGui(self): icon_path = ':/plugins/ecmwf_dissolve_tool/icon.png' self.add_action( icon_path, - text=self.tr(u'Dissolve Tool'), + text=self.tr(u'Add partner region'), callback=self.run, parent=self.iface.mainWindow()) @@ -178,7 +178,7 @@ def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu( - self.tr(u'&ECMWF dissolve tool'), + self.tr(u'&ECMWF'), action) self.iface.removeToolBarIcon(action) @@ -264,9 +264,12 @@ def run(self): partner_region_add = layers[selectedLayerIndex].layer().dataProvider().dataSourceUri() partner_region_add = partner_region_add[:partner_region_add.rfind('|')] + # debug only + """ self.iface.messageBar().pushMessage( "Info", "Partner region to add is in " + partner_region_add, level=Qgis.Info, duration=3) + """ outputvector = self.dlg.lineEdit.text() remaining_gaps = self.dlg.lineEdit_2.text() @@ -277,9 +280,12 @@ def run(self): if not grasscommand: self.iface.messageBar().pushMessage( "Error", "GRASS GIS not found", - level=Qgis.Critical, duration=3) + level=Qgis.Critical) + return - # install the GRASS addon v.ecmwf.adregions + Grass7Utils.startGrassSession() + + # install the GRASS addon v.ecmwf.addregion grassaddonpath = self.dlg.lineEdit_3.text() if grassaddonpath: gcommands = ("g.extension extension=v.ecmwf.addregion url='%s'" % grassaddonpath) @@ -302,6 +308,8 @@ def run(self): Grass7Utils.executeGrass([gcommands], feedback) + Grass7Utils.endGrassSession() + self.iface.messageBar().pushMessage( "Success", "Output vector written at " + outputvector, level=Qgis.Success, duration=3) diff --git a/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool_dialog.py b/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool_dialog.py index f6b8846..872cc74 100644 --- a/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool_dialog.py +++ b/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool_dialog.py @@ -3,7 +3,7 @@ /*************************************************************************** DissolveToolDialog A QGIS plugin - This plugin updates ECMWF partner regions and river basins + This plugin updates ECMWF partner regions Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ ------------------- begin : 2019-08-26 diff --git a/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool_dialog_base.ui b/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool_dialog_base.ui index 623a5e1..c5859b3 100644 --- a/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool_dialog_base.ui +++ b/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool_dialog_base.ui @@ -11,7 +11,7 @@ - ECMWF dissolve tool + ECMWF add new partner region diff --git a/QGIS_plugin/ecmwf_dissolve_tool/metadata.txt b/QGIS_plugin/ecmwf_dissolve_tool/metadata.txt index 6687baf..88c4b50 100644 --- a/QGIS_plugin/ecmwf_dissolve_tool/metadata.txt +++ b/QGIS_plugin/ecmwf_dissolve_tool/metadata.txt @@ -3,14 +3,14 @@ # This file should be included when you package your plugin.# Mandatory items: [general] -name=ECMWF dissolve tool +name=ECMWF add partner region qgisMinimumVersion=3.0 -description=This plugin updates ECMWF partner regions and river basins +description=This plugin updates ECMWF partner regions version=0.1 author=mundialis email=metz@mundialis.de -about=Update ECMWF partner regions with a new partner region and update the river basin network. +about=Update ECMWF partner regions with a new partner region. tracker=http://bugs repository=http://repo From 2e05f00a2cfd2232c4c6526e41831cefb5ff95a6 Mon Sep 17 00:00:00 2001 From: Markus Metz Date: Wed, 28 Aug 2019 14:28:45 +0200 Subject: [PATCH 03/12] ecmwf_gridpoints new QGIS plugin to update gridpoints --- QGIS_plugin/ecmwf_gridpoints/Makefile | 244 ++++++++++++++ QGIS_plugin/ecmwf_gridpoints/README.html | 42 +++ QGIS_plugin/ecmwf_gridpoints/README.txt | 32 ++ QGIS_plugin/ecmwf_gridpoints/__init__.py | 36 ++ .../ecmwf_gridpoints/ecmwf_gridpoints.py | 316 ++++++++++++++++++ .../ecmwf_gridpoints_dialog.py | 44 +++ .../ecmwf_gridpoints_dialog_base.ui | 261 +++++++++++++++ QGIS_plugin/ecmwf_gridpoints/help/Makefile | 130 +++++++ QGIS_plugin/ecmwf_gridpoints/help/make.bat | 155 +++++++++ .../ecmwf_gridpoints/help/source/conf.py | 216 ++++++++++++ .../ecmwf_gridpoints/help/source/index.rst | 20 ++ QGIS_plugin/ecmwf_gridpoints/i18n/af.ts | 11 + QGIS_plugin/ecmwf_gridpoints/icon.png | Bin 0 -> 1034 bytes QGIS_plugin/ecmwf_gridpoints/metadata.txt | 47 +++ QGIS_plugin/ecmwf_gridpoints/pb_tool.cfg | 80 +++++ QGIS_plugin/ecmwf_gridpoints/plugin_upload.py | 111 ++++++ QGIS_plugin/ecmwf_gridpoints/pylintrc | 281 ++++++++++++++++ QGIS_plugin/ecmwf_gridpoints/resources.py | 128 +++++++ QGIS_plugin/ecmwf_gridpoints/resources.qrc | 5 + .../scripts/compile-strings.sh | 12 + .../ecmwf_gridpoints/scripts/run-env-linux.sh | 28 ++ .../scripts/update-strings.sh | 56 ++++ QGIS_plugin/ecmwf_gridpoints/test/__init__.py | 2 + .../ecmwf_gridpoints/test/qgis_interface.py | 205 ++++++++++++ .../ecmwf_gridpoints/test/tenbytenraster.asc | 19 ++ .../test/tenbytenraster.asc.aux.xml | 13 + .../test/tenbytenraster.keywords | 1 + .../ecmwf_gridpoints/test/tenbytenraster.lic | 18 + .../ecmwf_gridpoints/test/tenbytenraster.prj | 1 + .../ecmwf_gridpoints/test/tenbytenraster.qml | 26 ++ .../test/test_ecmwf_basins_dialog.py | 55 +++ .../ecmwf_gridpoints/test/test_init.py | 64 ++++ .../test/test_qgis_environment.py | 60 ++++ .../ecmwf_gridpoints/test/test_resources.py | 44 +++ .../test/test_translations.py | 55 +++ .../ecmwf_gridpoints/test/utilities.py | 61 ++++ 36 files changed, 2879 insertions(+) create mode 100644 QGIS_plugin/ecmwf_gridpoints/Makefile create mode 100644 QGIS_plugin/ecmwf_gridpoints/README.html create mode 100644 QGIS_plugin/ecmwf_gridpoints/README.txt create mode 100644 QGIS_plugin/ecmwf_gridpoints/__init__.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints_dialog.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints_dialog_base.ui create mode 100644 QGIS_plugin/ecmwf_gridpoints/help/Makefile create mode 100644 QGIS_plugin/ecmwf_gridpoints/help/make.bat create mode 100644 QGIS_plugin/ecmwf_gridpoints/help/source/conf.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/help/source/index.rst create mode 100644 QGIS_plugin/ecmwf_gridpoints/i18n/af.ts create mode 100644 QGIS_plugin/ecmwf_gridpoints/icon.png create mode 100644 QGIS_plugin/ecmwf_gridpoints/metadata.txt create mode 100644 QGIS_plugin/ecmwf_gridpoints/pb_tool.cfg create mode 100755 QGIS_plugin/ecmwf_gridpoints/plugin_upload.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/pylintrc create mode 100644 QGIS_plugin/ecmwf_gridpoints/resources.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/resources.qrc create mode 100644 QGIS_plugin/ecmwf_gridpoints/scripts/compile-strings.sh create mode 100644 QGIS_plugin/ecmwf_gridpoints/scripts/run-env-linux.sh create mode 100644 QGIS_plugin/ecmwf_gridpoints/scripts/update-strings.sh create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/__init__.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/qgis_interface.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.asc create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.asc.aux.xml create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.keywords create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.lic create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.prj create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.qml create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/test_ecmwf_basins_dialog.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/test_init.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/test_qgis_environment.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/test_resources.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/test_translations.py create mode 100644 QGIS_plugin/ecmwf_gridpoints/test/utilities.py diff --git a/QGIS_plugin/ecmwf_gridpoints/Makefile b/QGIS_plugin/ecmwf_gridpoints/Makefile new file mode 100644 index 0000000..e421206 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/Makefile @@ -0,0 +1,244 @@ +#/*************************************************************************** +# Gridpoints +# +# This plugin updates ECMWF grid points +# ------------------- +# begin : 2019-08-26 +# git sha : $Format:%H$ +# copyright : (C) 2019 by mundialis +# email : metz@mundialis.de +# ***************************************************************************/ +# +#/*************************************************************************** +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU General Public License as published by * +# * the Free Software Foundation; either version 2 of the License, or * +# * (at your option) any later version. * +# * * +# ***************************************************************************/ + +################################################# +# Edit the following to match your sources lists +################################################# + + +#Add iso code for any locales you want to support here (space separated) +# default is no locales +# LOCALES = af +LOCALES = + +# If locales are enabled, set the name of the lrelease binary on your system. If +# you have trouble compiling the translations, you may have to specify the full path to +# lrelease +#LRELEASE = lrelease +#LRELEASE = lrelease-qt4 + + +# translation +SOURCES = \ + __init__.py \ + ecmwf_gridpoints.py ecmwf_gridpoints_dialog.py + +PLUGINNAME = ecmwf_gridpoints + +PY_FILES = \ + __init__.py \ + ecmwf_gridpoints.py ecmwf_gridpoints_dialog.py + +UI_FILES = ecmwf_gridpoints_dialog_base.ui + +EXTRAS = metadata.txt icon.png + +EXTRA_DIRS = + +COMPILED_RESOURCE_FILES = resources.py + +PEP8EXCLUDE=pydev,resources.py,conf.py,third_party,ui + +# QGISDIR points to the location where your plugin should be installed. +# This varies by platform, relative to your HOME directory: +# * Linux: +# .local/share/QGIS/QGIS3/profiles/default/python/plugins/ +# * Mac OS X: +# Library/Application Support/QGIS/QGIS3/profiles/default/python/plugins +# * Windows: +# AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins' + +QGISDIR=/home/metz/.local/share/QGIS/QGIS3/profiles/default/python/plugins/ + +################################################# +# Normally you would not need to edit below here +################################################# + +HELP = help/build/html + +PLUGIN_UPLOAD = $(c)/plugin_upload.py + +RESOURCE_SRC=$(shell grep '^ *@@g;s/.*>//g' | tr '\n' ' ') + +.PHONY: default +default: + @echo While you can use make to build and deploy your plugin, pb_tool + @echo is a much better solution. + @echo A Python script, pb_tool provides platform independent management of + @echo your plugins and runs anywhere. + @echo You can install pb_tool using: pip install pb_tool + @echo See https://g-sherman.github.io/plugin_build_tool/ for info. + +compile: $(COMPILED_RESOURCE_FILES) + +%.py : %.qrc $(RESOURCES_SRC) + pyrcc5 -o $*.py $< + +%.qm : %.ts + $(LRELEASE) $< + +test: compile transcompile + @echo + @echo "----------------------" + @echo "Regression Test Suite" + @echo "----------------------" + + @# Preceding dash means that make will continue in case of errors + @-export PYTHONPATH=`pwd`:$(PYTHONPATH); \ + export QGIS_DEBUG=0; \ + export QGIS_LOG_FILE=/dev/null; \ + nosetests -v --with-id --with-coverage --cover-package=. \ + 3>&1 1>&2 2>&3 3>&- || true + @echo "----------------------" + @echo "If you get a 'no module named qgis.core error, try sourcing" + @echo "the helper script we have provided first then run make test." + @echo "e.g. source run-env-linux.sh ; make test" + @echo "----------------------" + +deploy: compile doc transcompile + @echo + @echo "------------------------------------------" + @echo "Deploying plugin to your .qgis2 directory." + @echo "------------------------------------------" + # The deploy target only works on unix like operating system where + # the Python plugin directory is located at: + # $HOME/$(QGISDIR)/python/plugins + mkdir -p $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(PY_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(UI_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(COMPILED_RESOURCE_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(EXTRAS) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vfr i18n $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vfr $(HELP) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)/help + # Copy extra directories if any + (foreach EXTRA_DIR,(EXTRA_DIRS), cp -R (EXTRA_DIR) (HOME)/(QGISDIR)/python/plugins/(PLUGINNAME)/;) + + +# The dclean target removes compiled python files from plugin directory +# also deletes any .git entry +dclean: + @echo + @echo "-----------------------------------" + @echo "Removing any compiled python files." + @echo "-----------------------------------" + find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname "*.pyc" -delete + find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname ".git" -prune -exec rm -Rf {} \; + + +derase: + @echo + @echo "-------------------------" + @echo "Removing deployed plugin." + @echo "-------------------------" + rm -Rf $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + +zip: deploy dclean + @echo + @echo "---------------------------" + @echo "Creating plugin zip bundle." + @echo "---------------------------" + # The zip target deploys the plugin and creates a zip file with the deployed + # content. You can then upload the zip file on http://plugins.qgis.org + rm -f $(PLUGINNAME).zip + cd $(HOME)/$(QGISDIR)/python/plugins; zip -9r $(CURDIR)/$(PLUGINNAME).zip $(PLUGINNAME) + +package: compile + # Create a zip package of the plugin named $(PLUGINNAME).zip. + # This requires use of git (your plugin development directory must be a + # git repository). + # To use, pass a valid commit or tag as follows: + # make package VERSION=Version_0.3.2 + @echo + @echo "------------------------------------" + @echo "Exporting plugin to zip package. " + @echo "------------------------------------" + rm -f $(PLUGINNAME).zip + git archive --prefix=$(PLUGINNAME)/ -o $(PLUGINNAME).zip $(VERSION) + echo "Created package: $(PLUGINNAME).zip" + +upload: zip + @echo + @echo "-------------------------------------" + @echo "Uploading plugin to QGIS Plugin repo." + @echo "-------------------------------------" + $(PLUGIN_UPLOAD) $(PLUGINNAME).zip + +transup: + @echo + @echo "------------------------------------------------" + @echo "Updating translation files with any new strings." + @echo "------------------------------------------------" + @chmod +x scripts/update-strings.sh + @scripts/update-strings.sh $(LOCALES) + +transcompile: + @echo + @echo "----------------------------------------" + @echo "Compiled translation files to .qm files." + @echo "----------------------------------------" + @chmod +x scripts/compile-strings.sh + @scripts/compile-strings.sh $(LRELEASE) $(LOCALES) + +transclean: + @echo + @echo "------------------------------------" + @echo "Removing compiled translation files." + @echo "------------------------------------" + rm -f i18n/*.qm + +clean: + @echo + @echo "------------------------------------" + @echo "Removing uic and rcc generated files" + @echo "------------------------------------" + rm $(COMPILED_UI_FILES) $(COMPILED_RESOURCE_FILES) + +doc: + @echo + @echo "------------------------------------" + @echo "Building documentation using sphinx." + @echo "------------------------------------" + cd help; make html + +pylint: + @echo + @echo "-----------------" + @echo "Pylint violations" + @echo "-----------------" + @pylint --reports=n --rcfile=pylintrc . || true + @echo + @echo "----------------------" + @echo "If you get a 'no module named qgis.core' error, try sourcing" + @echo "the helper script we have provided first then run make pylint." + @echo "e.g. source run-env-linux.sh ; make pylint" + @echo "----------------------" + + +# Run pep8 style checking +#http://pypi.python.org/pypi/pep8 +pep8: + @echo + @echo "-----------" + @echo "PEP8 issues" + @echo "-----------" + @pep8 --repeat --ignore=E203,E121,E122,E123,E124,E125,E126,E127,E128 --exclude $(PEP8EXCLUDE) . || true + @echo "-----------" + @echo "Ignored in PEP8 check:" + @echo $(PEP8EXCLUDE) diff --git a/QGIS_plugin/ecmwf_gridpoints/README.html b/QGIS_plugin/ecmwf_gridpoints/README.html new file mode 100644 index 0000000..ab09fb8 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/README.html @@ -0,0 +1,42 @@ + + +

Plugin Builder Results

+ +Congratulations! You just built a plugin for QGIS!

+ +
+Your plugin gridpoints was created in:
+  /media/storage/Linux/apps/GRASS/ESoWC_challenge_2019/github_sample_data/gis_gridpoints/QGIS_plugin/ecmwf_gridpoints +

+Your QGIS plugin directory is located at:
+  /home/metz/.local/share/QGIS/QGIS3/profiles/default/python/plugins +

+

What's Next

+
    +
  1. If resources.py is not present in your plugin directory, compile the resources file using pyrcc5 (simply use pb_tool or make if you have automake) +
  2. Optionally, test the generated sources using make test (or run tests from your IDE) +
  3. Copy the entire directory containing your new plugin to the QGIS plugin directory (see Notes below) +
  4. Test the plugin by enabling it in the QGIS plugin manager +
  5. Customize it by editing the implementation file ecmwf_gridpoints.py +
  6. Create your own custom icon, replacing the default icon.png +
  7. Modify your user interface by opening ecmwf_gridpoints_dialog_base.ui in Qt Designer +
+Notes: +
    +
  • You can use pb_tool to compile, deploy, and manage your plugin. Tweak the pb_tool.cfg file included with your plugin as you add files. Install pb_tool using + pip or easy_install. See http://loc8.cc/pb_tool for more information. +
  • You can also use the Makefile to compile and deploy when you + make changes. This requires GNU make (gmake). The Makefile is ready to use, however you + will have to edit it to add addional Python source files, dialogs, and translations. +
+
+
+

+For information on writing PyQGIS code, see http://loc8.cc/pyqgis_resources for a list of resources. +

+
+

+©2011-2019 GeoApt LLC - geoapt.com +

+ + diff --git a/QGIS_plugin/ecmwf_gridpoints/README.txt b/QGIS_plugin/ecmwf_gridpoints/README.txt new file mode 100644 index 0000000..7dae233 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/README.txt @@ -0,0 +1,32 @@ +Plugin Builder Results + +Your plugin Update gridpoints was created in: + /media/storage/Linux/apps/GRASS/ESoWC_challenge_2019/github_sample_data/gis_dissolve_tool/QGIS_plugin/ecmwf_gridpoints + +Your QGIS plugin directory is located at: + /home/metz/.local/share/QGIS/QGIS3/profiles/default/python/plugins + +What's Next: + + * Copy the entire directory containing your new plugin to the QGIS plugin + directory + + * Compile the resources file using pyrcc5 + + * Run the tests (``make test``) + + * Test the plugin by enabling it in the QGIS plugin manager + + * Customize it by editing the implementation file: ``ecmwf_gridpoints.py`` + + * Create your own custom icon, replacing the default icon.png + + * Modify your user interface by opening gridpoints_dialog_base.ui in Qt Designer + + * You can use the Makefile to compile your Ui and resource files when + you make changes. This requires GNU make (gmake) + +For more information, see the PyQGIS Developer Cookbook at: +http://www.qgis.org/pyqgis-cookbook/index.html + +(C) 2011-2018 GeoApt LLC - geoapt.com diff --git a/QGIS_plugin/ecmwf_gridpoints/__init__.py b/QGIS_plugin/ecmwf_gridpoints/__init__.py new file mode 100644 index 0000000..0eba211 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + Gridpoints + A QGIS plugin + This plugin updates ECMWF grid points + Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ + ------------------- + begin : 2019-08-26 + copyright : (C) 2019 by mundialis + email : metz@mundialis.de + git sha : $Format:%H$ + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + This script initializes the plugin, making it known to QGIS. +""" + + +# noinspection PyPep8Naming +def classFactory(iface): # pylint: disable=invalid-name + """Load Gridpoints class from file Gridpoints. + + :param iface: A QGIS interface instance. + :type iface: QgsInterface + """ + # + from .ecmwf_gridpoints import Gridpoints + return Gridpoints(iface) diff --git a/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints.py b/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints.py new file mode 100644 index 0000000..6c6a5b7 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints.py @@ -0,0 +1,316 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + Gridpoints + A QGIS plugin + This plugin updates ECMWF grid points + Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ + ------------------- + begin : 2019-08-26 + git sha : $Format:%H$ + copyright : (C) 2019 by mundialis + email : metz@mundialis.de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" +from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtWidgets import QAction, QFileDialog +from qgis.core import QgsProject, Qgis, QgsProcessingFeedback + +# Initialize Qt resources from file resources.py +from .resources import * +# Import the code for the dialog +from .ecmwf_gridpoints_dialog import GridpointsDialog +import os.path + +import processing +from processing.algs.grass7.Grass7Utils import Grass7Utils + +class Gridpoints: + """QGIS Plugin Implementation.""" + + def __init__(self, iface): + """Constructor. + + :param iface: An interface instance that will be passed to this class + which provides the hook by which you can manipulate the QGIS + application at run time. + :type iface: QgsInterface + """ + # Save reference to the QGIS interface + self.iface = iface + # initialize plugin directory + self.plugin_dir = os.path.dirname(__file__) + # initialize locale + locale = QSettings().value('locale/userLocale')[0:2] + locale_path = os.path.join( + self.plugin_dir, + 'i18n', + 'Gridpoints_{}.qm'.format(locale)) + + if os.path.exists(locale_path): + self.translator = QTranslator() + self.translator.load(locale_path) + QCoreApplication.installTranslator(self.translator) + + # Declare instance attributes + self.actions = [] + self.menu = self.tr(u'&ECMWF') + + # Check if plugin was started the first time in current QGIS session + # Must be set in initGui() to survive plugin reloads + self.first_start = None + + # noinspection PyMethodMayBeStatic + def tr(self, message): + """Get the translation for a string using Qt translation API. + + We implement this ourselves since we do not inherit QObject. + + :param message: String for translation. + :type message: str, QString + + :returns: Translated version of message. + :rtype: QString + """ + # noinspection PyTypeChecker,PyArgumentList,PyCallByClass + return QCoreApplication.translate('Gridpoints', message) + + + def add_action( + self, + icon_path, + text, + callback, + enabled_flag=True, + add_to_menu=True, + add_to_toolbar=True, + status_tip=None, + whats_this=None, + parent=None): + """Add a toolbar icon to the toolbar. + + :param icon_path: Path to the icon for this action. Can be a resource + path (e.g. ':/plugins/foo/bar.png') or a normal file system path. + :type icon_path: str + + :param text: Text that should be shown in menu items for this action. + :type text: str + + :param callback: Function to be called when the action is triggered. + :type callback: function + + :param enabled_flag: A flag indicating if the action should be enabled + by default. Defaults to True. + :type enabled_flag: bool + + :param add_to_menu: Flag indicating whether the action should also + be added to the menu. Defaults to True. + :type add_to_menu: bool + + :param add_to_toolbar: Flag indicating whether the action should also + be added to the toolbar. Defaults to True. + :type add_to_toolbar: bool + + :param status_tip: Optional text to show in a popup when mouse pointer + hovers over the action. + :type status_tip: str + + :param parent: Parent widget for the new action. Defaults None. + :type parent: QWidget + + :param whats_this: Optional text to show in the status bar when the + mouse pointer hovers over the action. + + :returns: The action that was created. Note that the action is also + added to self.actions list. + :rtype: QAction + """ + + icon = QIcon(icon_path) + action = QAction(icon, text, parent) + action.triggered.connect(callback) + action.setEnabled(enabled_flag) + + if status_tip is not None: + action.setStatusTip(status_tip) + + if whats_this is not None: + action.setWhatsThis(whats_this) + + if add_to_toolbar: + # Adds plugin icon to Plugins toolbar + self.iface.addToolBarIcon(action) + + if add_to_menu: + self.iface.addPluginToMenu( + self.menu, + action) + + self.actions.append(action) + + return action + + def initGui(self): + """Create the menu entries and toolbar icons inside the QGIS GUI.""" + + icon_path = ':/plugins/ecmwf_gridpoints/icon.png' + self.add_action( + icon_path, + text=self.tr(u'Update gridpoints'), + callback=self.run, + parent=self.iface.mainWindow()) + + # will be set False in run() + self.first_start = True + + + def unload(self): + """Removes the plugin menu item and icon from QGIS GUI.""" + for action in self.actions: + self.iface.removePluginMenu( + self.tr(u'&ECMWF'), + action) + self.iface.removeToolBarIcon(action) + + + def select_column_ID(self): + colname = "MOU_IDS" + self.dlg.lineEdit_4.setText(colname) + + + def select_column_all_IDs(self): + colname = "MOUids_all" + self.dlg.lineEdit_5.setText(colname) + + + def select_output_vector(self): + filename, _filter = QFileDialog.getSaveFileName( + self.dlg, "Select output vector ","", '*.shp') + self.dlg.lineEdit.setText(filename) + + + def select_grass_addon(self): + dirname = QFileDialog.getExistingDirectory( + self.dlg, "Select directory ","/home", QFileDialog.ShowDirsOnly) + self.dlg.lineEdit_3.setText(dirname) + + + def run(self): + """Run method that performs all the real work""" + + # Create the dialog with elements (after translation) and keep reference + # Only create GUI ONCE in callback, so that it will only load when the plugin is started + if self.first_start == True: + self.first_start = False + self.dlg = GridpointsDialog() + self.dlg.pushButton.clicked.connect(self.select_output_vector) + self.dlg.pushButton_3.clicked.connect(self.select_grass_addon) + + + # Fetch the currently loaded layers + layers = QgsProject.instance().layerTreeRoot().children() + + # Clear the contents of the comboBox from previous runs + self.dlg.comboBox.clear() + # Populate the comboBox with names of all the loaded layers + self.dlg.comboBox.addItems([layer.name() for layer in layers]) + + # Clear the contents of the comboBox from previous runs + self.dlg.comboBox_2.clear() + # Populate the comboBox with names of all the loaded layers + self.dlg.comboBox_2.addItems([layer.name() for layer in layers]) + + # Clear the contents of the comboBox from previous runs + self.dlg.comboBox_3.clear() + # Populate the comboBox with names of all the loaded layers + self.dlg.comboBox_3.addItems([layer.name() for layer in layers]) + + val = self.dlg.lineEdit_4.text() + if val is None or len(val) == 0: + self.dlg.lineEdit_4.setText("MOU_IDS") + val = self.dlg.lineEdit_5.text() + if val is None or len(val) == 0: + self.dlg.lineEdit_5.setText("MOUids_all") + + # show the dialog + self.dlg.show() + # Run the dialog event loop + result = self.dlg.exec_() + # See if OK was pressed + if result: + # Do something useful here - delete the line containing pass and + # substitute with your code. + + feedback = QgsProcessingFeedback() + + # partner regions + selectedLayerIndex = self.dlg.comboBox.currentIndex() + partner_regions = layers[selectedLayerIndex].layer().dataProvider().dataSourceUri() + partner_regions = partner_regions[:partner_regions.rfind('|')] + + # grid points + selectedLayerIndex = self.dlg.comboBox_2.currentIndex() + grid_points = layers[selectedLayerIndex].layer().dataProvider().dataSourceUri() + grid_points = grid_points[:grid_points.rfind('|')] + + # basins + selectedLayerIndex = self.dlg.comboBox_3.currentIndex() + basins = layers[selectedLayerIndex].layer().dataProvider().dataSourceUri() + basins = basins[:basins.rfind('|')] + + # debug only + """ + self.iface.messageBar().pushMessage( + "Info", "Grid points to update is in " + grid_points, + level=Qgis.Info, duration=3) + """ + + outputvector = self.dlg.lineEdit.text() + pidcol = self.dlg.lineEdit_4.text() + allpidcol = self.dlg.lineEdit_5.text() + + grasscommand = Grass7Utils.grassBin() + if not grasscommand: + self.iface.messageBar().pushMessage( + "Error", "GRASS GIS not found", + level=Qgis.Critical) + return + + Grass7Utils.startGrassSession() + + # install the GRASS addon v.ecmwf.gridpoints + grassaddonpath = self.dlg.lineEdit_3.text() + if grassaddonpath: + gcommands = ("g.extension extension=v.ecmwf.gridpoints url='%s'" % grassaddonpath) + Grass7Utils.executeGrass([gcommands], feedback) + + gcommands = ("v.ecmwf.gridpoints partner_regions=\"%s\" grid_points=\"%s\" basins=\"%s\" output=\"%s\"" % + (partner_regions, grid_points, basins, outputvector)) + + if pidcol: + gcommands = gcommands + (" partner_id=%s " % pidcol) + + if allpidcol: + gcommands = gcommands + (" all_partner_id=%s " % allpidcol) + + # debug only + print("GRASS command:") + print("%s" % gcommands) + + Grass7Utils.executeGrass([gcommands], feedback) + + Grass7Utils.endGrassSession() + + self.iface.messageBar().pushMessage( + "Success", "Output vector written at " + outputvector, + level=Qgis.Success, duration=3) diff --git a/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints_dialog.py b/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints_dialog.py new file mode 100644 index 0000000..32e1b6e --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints_dialog.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + GridpointsDialog + A QGIS plugin + This plugin updates ECMWF grid points + Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ + ------------------- + begin : 2019-08-26 + git sha : $Format:%H$ + copyright : (C) 2019 by mundialis + email : metz@mundialis.de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +import os + +from qgis.PyQt import uic +from qgis.PyQt import QtWidgets + +# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer +FORM_CLASS, _ = uic.loadUiType(os.path.join( + os.path.dirname(__file__), 'ecmwf_gridpoints_dialog_base.ui')) + + +class GridpointsDialog(QtWidgets.QDialog, FORM_CLASS): + def __init__(self, parent=None): + """Constructor.""" + super(GridpointsDialog, self).__init__(parent) + # Set up the user interface from Designer through FORM_CLASS. + # After self.setupUi() you can access any designer object by doing + # self., and you can use autoconnect slots - see + # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html + # #widgets-and-dialogs-with-auto-connect + self.setupUi(self) diff --git a/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints_dialog_base.ui b/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints_dialog_base.ui new file mode 100644 index 0000000..a06099a --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints_dialog_base.ui @@ -0,0 +1,261 @@ + + + GridpointsDialogBase + + + + 0 + 0 + 623 + 499 + + + + ECMWF grid points + + + + + 260 + 450 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 340 + 30 + 231 + 27 + + + + + + + 30 + 30 + 271 + 19 + + + + Partner regions + + + + + + 340 + 80 + 231 + 27 + + + + + + + 30 + 80 + 261 + 20 + + + + Grid points + + + + + + 30 + 330 + 271 + 19 + + + + Updated grid points + + + + + + 340 + 320 + 181 + 27 + + + + + + + 540 + 320 + 31 + 27 + + + + ... + + + + + + 30 + 370 + 281 + 19 + + + + Folder with v.ecmwf.gridpoints + + + + + + 340 + 370 + 181 + 27 + + + + + + + 540 + 370 + 31 + 27 + + + + ... + + + + + + 30 + 200 + 221 + 19 + + + + <html><head/><body><p>default: MOU_IDS</p></body></html> + + + Column with partner ID + + + + + + 30 + 240 + 271 + 19 + + + + <html><head/><body><p>default: MOUids_all</p></body></html> + + + Column with all partner IDs + + + + + + 410 + 200 + 161 + 27 + + + + + + + 410 + 240 + 161 + 27 + + + + + + + 30 + 130 + 241 + 16 + + + + Basins + + + + + + 340 + 130 + 231 + 23 + + + + + + + + button_box + accepted() + GridpointsDialogBase + accept() + + + 20 + 20 + + + 20 + 20 + + + + + button_box + rejected() + GridpointsDialogBase + reject() + + + 20 + 20 + + + 20 + 20 + + + + + diff --git a/QGIS_plugin/ecmwf_gridpoints/help/Makefile b/QGIS_plugin/ecmwf_gridpoints/help/Makefile new file mode 100644 index 0000000..9def777 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/help/Makefile @@ -0,0 +1,130 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/template_class.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/template_class.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/template_class" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/template_class" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/QGIS_plugin/ecmwf_gridpoints/help/make.bat b/QGIS_plugin/ecmwf_gridpoints/help/make.bat new file mode 100644 index 0000000..3377610 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/help/make.bat @@ -0,0 +1,155 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\template_class.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\template_class.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/QGIS_plugin/ecmwf_gridpoints/help/source/conf.py b/QGIS_plugin/ecmwf_gridpoints/help/source/conf.py new file mode 100644 index 0000000..099fb11 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/help/source/conf.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +# +# Update gridpoints documentation build configuration file, created by +# sphinx-quickstart on Sun Feb 12 17:11:03 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.todo', 'sphinx.ext.imgmath', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Gridpoints' +copyright = u'2013, mundialis' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_TemplateModuleNames = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'TemplateClassdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'gridpoints.tex', u'gridpoints Documentation', + u'mundialis', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'TemplateClass', u'gridpoints Documentation', + [u'mundialis'], 1) +] diff --git a/QGIS_plugin/ecmwf_gridpoints/help/source/index.rst b/QGIS_plugin/ecmwf_gridpoints/help/source/index.rst new file mode 100644 index 0000000..8cdcd35 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/help/source/index.rst @@ -0,0 +1,20 @@ +.. gridpoints documentation master file, created by + sphinx-quickstart on Sun Feb 12 17:11:03 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to gridpoints's documentation! +============================================ + +Contents: + +.. toctree:: + :maxdepth: 2 + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/QGIS_plugin/ecmwf_gridpoints/i18n/af.ts b/QGIS_plugin/ecmwf_gridpoints/i18n/af.ts new file mode 100644 index 0000000..615a88c --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/i18n/af.ts @@ -0,0 +1,11 @@ + + + + @default + + + Good morning + Goeie more + + + diff --git a/QGIS_plugin/ecmwf_gridpoints/icon.png b/QGIS_plugin/ecmwf_gridpoints/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f696c00303219cfcc2f0d57608d04e298fbf56c3 GIT binary patch literal 1034 zcmV+l1oiugP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXD@ z77;9+NQ1io00W9iL_t(Y$E}r1Xk1ko$A9PCduQg(OlBrY(9Ks7iMEcmzAKc3;VzZCAM*45PN_;^e6>czpi>a)k;jWgn$XwX#(ffW0x= z+8`f&KFz}YS*DXYlF=gSn-KxzBG6N#o}Sbu5ylW$ifX;y!Gxcoccb7X?l*&Cg%BLp+upi(AsPbFBN>WXDC?; z&(>7p1`~%XZ)m0`uQ4(<$FhGH?|poXGiT2da3o4;)@nx9({OUBf@d^Etti_X6*QRt zM(yu64Vk}6JTN%G$V;!1TAbqGBW-lFx`bT|DMfv_l!BJF5c985o@kjA(}*tNFK{QCU>`#$u@1vR=}>qa5@&kCuGhvP`xkhN}@YN}=O zQj$Y2Uc!t58W8mdtiyG1gOY%=`Oqf#kFzPpKDdk-HFaxyemu%W41P&5^jJIh_B4=) zIRqS8AO73bJYStlGW|rdy4U))V9(wb+>k{~N-CaW{!Ycn!A{zChUn=E(`kQe>l33Is7?7;qutHE1STpm8YoYyRmhySVLwneD zILtlW(W=jJRx_{aH|L?0Ar`gK1}rHy^>Uk*mB7T>op^&oQzC$62^{-|!2sta za1=;y9wJfWb=kJ1ful09`}7PjxUqtpunAx=vHSGQzo6-Eu3FzqfdBvi07*qoM6N<$ Ef=x2zx&QzG literal 0 HcmV?d00001 diff --git a/QGIS_plugin/ecmwf_gridpoints/metadata.txt b/QGIS_plugin/ecmwf_gridpoints/metadata.txt new file mode 100644 index 0000000..117c03e --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/metadata.txt @@ -0,0 +1,47 @@ +# This file contains metadata for your plugin. + +# This file should be included when you package your plugin.# Mandatory items: + +[general] +name=ECMWF gridpoints +qgisMinimumVersion=3.0 +description=This plugin updates ECMWF grid points +version=0.1 +author=mundialis +email=metz@mundialis.de + +about=Update ECMWF grid points. + +tracker=http://bugs +repository=http://repo +# End of mandatory metadata + +# Recommended items: + +hasProcessingProvider=no +# Uncomment the following line and add your changelog: +# changelog= + +# Tags are comma separated with spaces allowed +tags=python + +homepage=http://homepage +category=Plugins +icon=icon.png +# experimental flag +experimental=True + +# deprecated flag (applies to the whole plugin, not just a single version) +deprecated=False + +# Since QGIS 3.8, a comma separated list of plugins to be installed +# (or upgraded) can be specified. +# Check the documentation for more information. +# plugin_dependencies= + +Category of the plugin: Raster, Vector, Database or Web +# category= + +# If the plugin can run on QGIS Server. +server=False + diff --git a/QGIS_plugin/ecmwf_gridpoints/pb_tool.cfg b/QGIS_plugin/ecmwf_gridpoints/pb_tool.cfg new file mode 100644 index 0000000..13d7330 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/pb_tool.cfg @@ -0,0 +1,80 @@ +#/*************************************************************************** +# Gridpoints +# +# Configuration file for plugin builder tool (pb_tool) +# Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ +# ------------------- +# begin : 2019-08-26 +# copyright : (C) 2019 by mundialis +# email : metz@mundialis.de +# ***************************************************************************/ +# +#/*************************************************************************** +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU General Public License as published by * +# * the Free Software Foundation; either version 2 of the License, or * +# * (at your option) any later version. * +# * * +# ***************************************************************************/ +# +# +# You can install pb_tool using: +# pip install http://geoapt.net/files/pb_tool.zip +# +# Consider doing your development (and install of pb_tool) in a virtualenv. +# +# For details on setting up and using pb_tool, see: +# http://g-sherman.github.io/plugin_build_tool/ +# +# Issues and pull requests here: +# https://github.com/g-sherman/plugin_build_tool: +# +# Sane defaults for your plugin generated by the Plugin Builder are +# already set below. +# +# As you add Python source files and UI files to your plugin, add +# them to the appropriate [files] section below. + +[plugin] +# Name of the plugin. This is the name of the directory that will +# be created in .qgis2/python/plugins +name: ecmwf_gridpoints + +# Full path to where you want your plugin directory copied. If empty, +# the QGIS default path will be used. Don't include the plugin name in +# the path. +plugin_path: + +[files] +# Python files that should be deployed with the plugin +python_files: __init__.py ecmwf_gridpoints.py ecmwf_gridpoints_dialog.py + +# The main dialog file that is loaded (not compiled) +main_dialog: ecmwf_gridpoints_dialog_base.ui + +# Other ui files for dialogs you create (these will be compiled) +compiled_ui_files: + +# Resource file(s) that will be compiled +resource_files: resources.qrc + +# Other files required for the plugin +extras: metadata.txt icon.png + +# Other directories to be deployed with the plugin. +# These must be subdirectories under the plugin directory +extra_dirs: + +# ISO code(s) for any locales (translations), separated by spaces. +# Corresponding .ts files must exist in the i18n directory +locales: + +[help] +# the built help directory that should be deployed with the plugin +dir: help/build/html +# the name of the directory to target in the deployed plugin +target: help + + + diff --git a/QGIS_plugin/ecmwf_gridpoints/plugin_upload.py b/QGIS_plugin/ecmwf_gridpoints/plugin_upload.py new file mode 100755 index 0000000..a88ea2b --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/plugin_upload.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# coding=utf-8 +"""This script uploads a plugin package to the plugin repository. + Authors: A. Pasotti, V. Picavet + git sha : $TemplateVCSFormat +""" + +import sys +import getpass +import xmlrpc.client +from optparse import OptionParser + +standard_library.install_aliases() + +# Configuration +PROTOCOL = 'https' +SERVER = 'plugins.qgis.org' +PORT = '443' +ENDPOINT = '/plugins/RPC2/' +VERBOSE = False + + +def main(parameters, arguments): + """Main entry point. + + :param parameters: Command line parameters. + :param arguments: Command line arguments. + """ + address = "{protocol}://{username}:{password}@{server}:{port}{endpoint}".format( + protocol=PROTOCOL, + username=parameters.username, + password=parameters.password, + server=parameters.server, + port=parameters.port, + endpoint=ENDPOINT) + print("Connecting to: %s" % hide_password(address)) + + server = xmlrpc.client.ServerProxy(address, verbose=VERBOSE) + + try: + with open(arguments[0], 'rb') as handle: + plugin_id, version_id = server.plugin.upload( + xmlrpc.client.Binary(handle.read())) + print("Plugin ID: %s" % plugin_id) + print("Version ID: %s" % version_id) + except xmlrpc.client.ProtocolError as err: + print("A protocol error occurred") + print("URL: %s" % hide_password(err.url, 0)) + print("HTTP/HTTPS headers: %s" % err.headers) + print("Error code: %d" % err.errcode) + print("Error message: %s" % err.errmsg) + except xmlrpc.client.Fault as err: + print("A fault occurred") + print("Fault code: %d" % err.faultCode) + print("Fault string: %s" % err.faultString) + + +def hide_password(url, start=6): + """Returns the http url with password part replaced with '*'. + + :param url: URL to upload the plugin to. + :type url: str + + :param start: Position of start of password. + :type start: int + """ + start_position = url.find(':', start) + 1 + end_position = url.find('@') + return "%s%s%s" % ( + url[:start_position], + '*' * (end_position - start_position), + url[end_position:]) + + +if __name__ == "__main__": + parser = OptionParser(usage="%prog [options] plugin.zip") + parser.add_option( + "-w", "--password", dest="password", + help="Password for plugin site", metavar="******") + parser.add_option( + "-u", "--username", dest="username", + help="Username of plugin site", metavar="user") + parser.add_option( + "-p", "--port", dest="port", + help="Server port to connect to", metavar="80") + parser.add_option( + "-s", "--server", dest="server", + help="Specify server name", metavar="plugins.qgis.org") + options, args = parser.parse_args() + if len(args) != 1: + print("Please specify zip file.\n") + parser.print_help() + sys.exit(1) + if not options.server: + options.server = SERVER + if not options.port: + options.port = PORT + if not options.username: + # interactive mode + username = getpass.getuser() + print("Please enter user name [%s] :" % username, end=' ') + + res = input() + if res != "": + options.username = res + else: + options.username = username + if not options.password: + # interactive mode + options.password = getpass.getpass() + main(options, args) diff --git a/QGIS_plugin/ecmwf_gridpoints/pylintrc b/QGIS_plugin/ecmwf_gridpoints/pylintrc new file mode 100644 index 0000000..7e168f6 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/pylintrc @@ -0,0 +1,281 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# see http://stackoverflow.com/questions/21487025/pylint-locally-defined-disables-still-give-warnings-how-to-suppress-them +disable=locally-disabled,C0103 + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct attribute names in class +# bodies +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/QGIS_plugin/ecmwf_gridpoints/resources.py b/QGIS_plugin/ecmwf_gridpoints/resources.py new file mode 100644 index 0000000..5528658 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/resources.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created by: The Resource Compiler for PyQt5 (Qt v5.12.4) +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore + +qt_resource_data = b"\ +\x00\x00\x04\x0a\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x17\x00\x00\x00\x18\x08\x06\x00\x00\x00\x11\x7c\x66\x75\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd9\x02\x15\ +\x16\x11\x2c\x9d\x48\x83\xbb\x00\x00\x03\x8a\x49\x44\x41\x54\x48\ +\xc7\xad\x95\x4b\x68\x5c\x55\x18\xc7\x7f\xe7\xdc\x7b\x67\xe6\xce\ +\x4c\x66\x26\x49\xd3\x24\x26\xa6\xc6\xf8\x40\x21\xa5\x04\xb3\x28\ +\xda\x98\x20\xa5\x0b\xad\x55\xa8\x2b\xc5\x50\x1f\xa0\x6e\x34\x2b\ +\x45\x30\x14\x02\xba\x52\x69\x15\x17\x66\x63\x45\x97\x95\xa0\xad\ +\x0b\xfb\xc0\x06\x25\xb6\x71\x61\x12\x41\x50\xdb\x2a\x21\xd1\xe2\ +\x24\xf3\x9e\xc9\xcc\xbd\xe7\x1c\x17\x35\x43\x1e\x33\x21\xb6\xfd\ +\x56\x87\xf3\x9d\xfb\xfb\x1e\xf7\xff\x9d\x23\x8c\x31\x43\x95\xf4\ +\x85\x1e\x3f\x3b\x35\xac\xfd\xcc\x43\xdc\xa4\x49\x3b\xfe\x9d\x1d\ +\xdb\x7b\x22\x90\x78\xf8\xb2\x28\xa7\xbe\x7d\xc1\x4b\x9d\x79\xdf\ +\x18\x15\xe5\x16\x99\x10\x56\xde\x69\xdc\x3f\x22\xfd\xec\xd4\xf0\ +\xad\x04\x03\x18\xa3\xa2\x7e\x76\x6a\x58\xde\x68\x2b\xb4\x36\xf8\ +\xbe\xc6\x18\x53\xdb\xef\xe7\xfa\xec\xed\x67\x63\x10\x42\x00\xf0\ +\xfb\xd5\x65\x2a\x15\x45\xc7\x6d\x0d\x00\xc4\xa2\xc1\xaa\x6f\x0d\ +\x3e\x6c\xab\xc2\x1c\x56\xa4\x77\x4b\xb0\xf2\x35\x15\x5f\x21\x85\ +\xe0\xc8\x6b\x5f\x92\x2d\x37\x33\x39\xf9\x03\x27\x8e\x1f\xa2\xf7\ +\xbe\x9d\x04\x1c\x0b\x37\xe4\xac\xff\xa6\x30\x87\xbd\xba\x00\x6a\ +\x06\x79\xe5\xf5\xaf\x89\xd9\x92\xc5\xcc\x0a\xd9\x7c\x19\xcf\xe9\ +\xe2\xe4\xa9\x2f\x78\x7c\xff\x01\x72\x85\x0a\x2b\x65\x1f\xa5\x4c\ +\xb5\xb2\x55\x16\x80\xbd\x31\xda\xda\x20\x1f\x7d\x3e\xcd\xc2\xfd\ +\x59\xa6\x93\x39\x92\xd1\x22\xea\x9b\x16\xce\x9d\x3f\xce\xe0\x83\ +\x03\x24\x82\x59\x3a\xdb\x7b\x88\xc7\x82\x68\x63\x58\xc9\xcc\x62\ +\x8c\x21\x18\xb0\x6a\xc3\x37\x06\x49\x16\xff\x24\x6b\xa5\x49\xbb\ +\x25\xbc\xa2\xa6\x21\xbb\x40\x7f\xdf\x00\x83\xbd\x01\x8e\x3c\xd5\ +\x45\xd7\x8e\x6b\x9c\x9c\x98\x25\x1a\xb6\xe8\xbe\x3d\xc2\xdd\x77\ +\x44\x48\xc4\x1c\x22\xe1\xeb\x58\x59\xaf\xcf\xd3\x33\x29\x2e\x34\ +\x2d\x91\x93\x3e\xbe\x34\x78\x01\xc5\xe2\x61\xc5\xae\x72\x8e\x70\ +\xc8\xc2\x0d\x5a\xbc\xf5\xee\x2f\x9c\xfa\x3e\x86\x69\x7a\x8e\xcf\ +\x26\xe6\xf9\x63\xa1\x44\xa1\xa4\xd0\xda\x6c\x0d\x2f\x15\x7c\xb4\ +\x67\x28\x59\x0a\xcf\xd6\x54\xe2\x06\x13\x87\x2b\x6f\x68\xa6\x27\ +\xaf\x31\x32\x36\xc7\xb2\x7f\x17\xef\x7d\x7c\x8c\x33\x67\xcf\x12\ +\x70\x24\x4a\x69\xd6\x6a\x46\xd6\xd3\x70\x72\xa9\x82\x67\x34\x45\ +\xad\x28\xdb\x1a\x15\x34\x98\xff\x46\xed\xef\x37\x0d\x99\xbf\x4a\ +\x3c\x30\x38\xc0\xc8\x4b\xaf\x92\x5a\x9c\xe2\xe0\x23\x6d\x74\xb4\ +\xba\x84\x5d\x0b\x29\x45\x7d\xb8\x94\x82\x96\xb6\x10\xf3\xc5\x12\ +\x2a\xef\x53\x11\x1a\x63\xad\x3f\x93\x19\x85\xf1\xb1\x77\x58\x5a\ +\xf8\x99\x97\x9f\xe9\xa6\x75\x47\x90\xc6\xb8\x43\xd8\xb5\xb6\xce\ +\xfc\xfa\xfd\x00\xfb\x3e\xf4\xc8\x05\x35\xba\x5e\xeb\x46\x21\xf9\ +\xcf\x0a\xa9\x8c\x87\xe3\x48\xdc\x90\xb5\x6e\x98\x6a\xaa\x65\xf2\ +\x52\x92\x43\x2f\x5e\xc2\x8c\x02\x1a\x10\xf5\x07\xac\xc3\x75\x70\ +\x83\x92\x80\xb3\xf9\xd0\x26\xf8\x8f\xb3\x29\xc6\x3e\xb8\x8c\x19\ +\x35\x75\x6b\x7b\x7e\x3c\xca\x45\x0c\x7e\x49\x31\xf4\x58\x3b\xf7\ +\xf6\x34\x90\x88\x39\x04\x1c\x59\x1f\xfe\xdb\xd5\x3c\x5f\x9d\x4b\ +\x32\xfd\x44\xb2\xba\xd7\xfa\xb6\x60\xcf\xde\x16\xdc\x90\x45\x4c\ +\x4a\x2a\x9e\x62\xfe\x4e\xc5\xc8\xc1\x4e\xda\x76\x86\xe8\xe9\x0a\ +\xe3\xd8\x92\x58\xd4\xc6\xb2\x44\x6d\x78\x2a\x53\xe1\xca\x7c\x99\ +\x63\x5d\xbf\x56\x9d\xbd\x9f\x44\x18\x7a\xba\x95\x27\x0f\xb4\xd3\ +\xdc\x18\xc0\xf3\x0d\x52\x40\xd8\xb5\xb0\xa4\x20\x14\xb2\x70\x6c\ +\x81\x63\xcb\xaa\x42\xd6\xfd\xb7\xf4\xec\xa3\x06\xa0\x50\x52\xd8\ +\x4e\x1b\x7e\x4a\xd3\x31\xf9\x29\xcf\xfe\xd4\x49\x7f\x5f\x13\xfb\ +\xfa\x9b\x71\x43\x92\x58\xd4\x21\x18\x90\xac\xde\xb0\x42\x50\x13\ +\x58\x33\xf3\x88\x6b\xa1\xfd\x65\x96\xf2\x79\xc6\x43\x7b\xd8\x75\ +\x38\xcc\x3d\xdd\xd1\xaa\xcf\x71\xe4\xff\x7f\x91\x56\x33\xaf\xea\ +\x37\xe7\xa1\x94\x21\x16\xb5\xd1\x06\x2c\x29\x36\xf5\x72\x9b\x96\ +\x95\xc0\xc4\xda\x9d\x78\x83\x43\x53\x22\x80\x65\x09\x1c\xfb\x86\ +\xc1\x00\xe7\x25\x70\x14\x48\x6f\x1e\x22\x51\xe3\x75\xd9\xb6\xa5\ +\x81\xa3\x32\xb1\xfb\xf4\x0c\x30\xb8\xb1\x82\x9b\xb0\x09\x60\x30\ +\xb1\xfb\xf4\xcc\xbf\xa0\xe9\x6e\xae\x5a\xdf\x4b\x81\x00\x00\x00\ +\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +" + +qt_resource_name = b"\ +\x00\x07\ +\x07\x3b\xe0\xb3\ +\x00\x70\ +\x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\ +\x00\x10\ +\x00\xe4\xfd\x73\ +\x00\x65\ +\x00\x63\x00\x6d\x00\x77\x00\x66\x00\x5f\x00\x67\x00\x72\x00\x69\x00\x64\x00\x70\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\ +\x00\x08\ +\x0a\x61\x5a\xa7\ +\x00\x69\ +\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +" + +qt_resource_struct_v1 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ +\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +" + +qt_resource_struct_v2 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x6c\xd3\xbe\x16\x94\ +" + +qt_version = [int(v) for v in QtCore.qVersion().split('.')] +if qt_version < [5, 8, 0]: + rcc_version = 1 + qt_resource_struct = qt_resource_struct_v1 +else: + rcc_version = 2 + qt_resource_struct = qt_resource_struct_v2 + +def qInitResources(): + QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/QGIS_plugin/ecmwf_gridpoints/resources.qrc b/QGIS_plugin/ecmwf_gridpoints/resources.qrc new file mode 100644 index 0000000..26f0a8e --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/resources.qrc @@ -0,0 +1,5 @@ + + + icon.png + + diff --git a/QGIS_plugin/ecmwf_gridpoints/scripts/compile-strings.sh b/QGIS_plugin/ecmwf_gridpoints/scripts/compile-strings.sh new file mode 100644 index 0000000..9d76083 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/scripts/compile-strings.sh @@ -0,0 +1,12 @@ +#!/bin/bash +LRELEASE=$1 +LOCALES=$2 + + +for LOCALE in ${LOCALES} +do + echo "Processing: ${LOCALE}.ts" + # Note we don't use pylupdate with qt .pro file approach as it is flakey + # about what is made available. + $LRELEASE i18n/${LOCALE}.ts +done diff --git a/QGIS_plugin/ecmwf_gridpoints/scripts/run-env-linux.sh b/QGIS_plugin/ecmwf_gridpoints/scripts/run-env-linux.sh new file mode 100644 index 0000000..668247c --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/scripts/run-env-linux.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +QGIS_PREFIX_PATH=/usr/local/qgis-2.0 +if [ -n "$1" ]; then + QGIS_PREFIX_PATH=$1 +fi + +echo ${QGIS_PREFIX_PATH} + + +export QGIS_PREFIX_PATH=${QGIS_PREFIX_PATH} +export QGIS_PATH=${QGIS_PREFIX_PATH} +export LD_LIBRARY_PATH=${QGIS_PREFIX_PATH}/lib +export PYTHONPATH=${QGIS_PREFIX_PATH}/share/qgis/python:${QGIS_PREFIX_PATH}/share/qgis/python/plugins:${PYTHONPATH} + +echo "QGIS PATH: $QGIS_PREFIX_PATH" +export QGIS_DEBUG=0 +export QGIS_LOG_FILE=/tmp/inasafe/realtime/logs/qgis.log + +export PATH=${QGIS_PREFIX_PATH}/bin:$PATH + +echo "This script is intended to be sourced to set up your shell to" +echo "use a QGIS 2.0 built in $QGIS_PREFIX_PATH" +echo +echo "To use it do:" +echo "source $BASH_SOURCE /your/optional/install/path" +echo +echo "Then use the make file supplied here e.g. make guitest" diff --git a/QGIS_plugin/ecmwf_gridpoints/scripts/update-strings.sh b/QGIS_plugin/ecmwf_gridpoints/scripts/update-strings.sh new file mode 100644 index 0000000..a31f712 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/scripts/update-strings.sh @@ -0,0 +1,56 @@ +#!/bin/bash +LOCALES=$* + +# Get newest .py files so we don't update strings unnecessarily + +CHANGED_FILES=0 +PYTHON_FILES=`find . -regex ".*\(ui\|py\)$" -type f` +for PYTHON_FILE in $PYTHON_FILES +do + CHANGED=$(stat -c %Y $PYTHON_FILE) + if [ ${CHANGED} -gt ${CHANGED_FILES} ] + then + CHANGED_FILES=${CHANGED} + fi +done + +# Qt translation stuff +# for .ts file +UPDATE=false +for LOCALE in ${LOCALES} +do + TRANSLATION_FILE="i18n/$LOCALE.ts" + if [ ! -f ${TRANSLATION_FILE} ] + then + # Force translation string collection as we have a new language file + touch ${TRANSLATION_FILE} + UPDATE=true + break + fi + + MODIFICATION_TIME=$(stat -c %Y ${TRANSLATION_FILE}) + if [ ${CHANGED_FILES} -gt ${MODIFICATION_TIME} ] + then + # Force translation string collection as a .py file has been updated + UPDATE=true + break + fi +done + +if [ ${UPDATE} == true ] +# retrieve all python files +then + echo ${PYTHON_FILES} + # update .ts + echo "Please provide translations by editing the translation files below:" + for LOCALE in ${LOCALES} + do + echo "i18n/"${LOCALE}".ts" + # Note we don't use pylupdate with qt .pro file approach as it is flakey + # about what is made available. + pylupdate4 -noobsolete ${PYTHON_FILES} -ts i18n/${LOCALE}.ts + done +else + echo "No need to edit any translation files (.ts) because no python files" + echo "has been updated since the last update translation. " +fi diff --git a/QGIS_plugin/ecmwf_gridpoints/test/__init__.py b/QGIS_plugin/ecmwf_gridpoints/test/__init__.py new file mode 100644 index 0000000..8feeb0b --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/__init__.py @@ -0,0 +1,2 @@ +# import qgis libs so that ve set the correct sip api version +import qgis # pylint: disable=W0611 # NOQA \ No newline at end of file diff --git a/QGIS_plugin/ecmwf_gridpoints/test/qgis_interface.py b/QGIS_plugin/ecmwf_gridpoints/test/qgis_interface.py new file mode 100644 index 0000000..a407052 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/qgis_interface.py @@ -0,0 +1,205 @@ +# coding=utf-8 +"""QGIS plugin implementation. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +.. note:: This source code was copied from the 'postgis viewer' application + with original authors: + Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk + Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org + Copyright (c) 2014 Tim Sutton, tim@linfiniti.com + +""" + +__author__ = 'tim@linfiniti.com' +__revision__ = '$Format:%H$' +__date__ = '10/01/2011' +__copyright__ = ( + 'Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and ' + 'Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org' + 'Copyright (c) 2014 Tim Sutton, tim@linfiniti.com' +) + +import logging +from qgis.PyQt.QtCore import QObject, pyqtSlot, pyqtSignal +from qgis.core import QgsMapLayerRegistry +from qgis.gui import QgsMapCanvasLayer +LOGGER = logging.getLogger('QGIS') + + +#noinspection PyMethodMayBeStatic,PyPep8Naming +class QgisInterface(QObject): + """Class to expose QGIS objects and functions to plugins. + + This class is here for enabling us to run unit tests only, + so most methods are simply stubs. + """ + currentLayerChanged = pyqtSignal(QgsMapCanvasLayer) + + def __init__(self, canvas): + """Constructor + :param canvas: + """ + QObject.__init__(self) + self.canvas = canvas + # Set up slots so we can mimic the behaviour of QGIS when layers + # are added. + LOGGER.debug('Initialising canvas...') + # noinspection PyArgumentList + QgsMapLayerRegistry.instance().layersAdded.connect(self.addLayers) + # noinspection PyArgumentList + QgsMapLayerRegistry.instance().layerWasAdded.connect(self.addLayer) + # noinspection PyArgumentList + QgsMapLayerRegistry.instance().removeAll.connect(self.removeAllLayers) + + # For processing module + self.destCrs = None + + @pyqtSlot('QStringList') + def addLayers(self, layers): + """Handle layers being added to the registry so they show up in canvas. + + :param layers: list list of map layers that were added + + .. note:: The QgsInterface api does not include this method, + it is added here as a helper to facilitate testing. + """ + #LOGGER.debug('addLayers called on qgis_interface') + #LOGGER.debug('Number of layers being added: %s' % len(layers)) + #LOGGER.debug('Layer Count Before: %s' % len(self.canvas.layers())) + current_layers = self.canvas.layers() + final_layers = [] + for layer in current_layers: + final_layers.append(QgsMapCanvasLayer(layer)) + for layer in layers: + final_layers.append(QgsMapCanvasLayer(layer)) + + self.canvas.setLayerSet(final_layers) + #LOGGER.debug('Layer Count After: %s' % len(self.canvas.layers())) + + @pyqtSlot('QgsMapLayer') + def addLayer(self, layer): + """Handle a layer being added to the registry so it shows up in canvas. + + :param layer: list list of map layers that were added + + .. note: The QgsInterface api does not include this method, it is added + here as a helper to facilitate testing. + + .. note: The addLayer method was deprecated in QGIS 1.8 so you should + not need this method much. + """ + pass + + @pyqtSlot() + def removeAllLayers(self): + """Remove layers from the canvas before they get deleted.""" + self.canvas.setLayerSet([]) + + def newProject(self): + """Create new project.""" + # noinspection PyArgumentList + QgsMapLayerRegistry.instance().removeAllMapLayers() + + # ---------------- API Mock for QgsInterface follows ------------------- + + def zoomFull(self): + """Zoom to the map full extent.""" + pass + + def zoomToPrevious(self): + """Zoom to previous view extent.""" + pass + + def zoomToNext(self): + """Zoom to next view extent.""" + pass + + def zoomToActiveLayer(self): + """Zoom to extent of active layer.""" + pass + + def addVectorLayer(self, path, base_name, provider_key): + """Add a vector layer. + + :param path: Path to layer. + :type path: str + + :param base_name: Base name for layer. + :type base_name: str + + :param provider_key: Provider key e.g. 'ogr' + :type provider_key: str + """ + pass + + def addRasterLayer(self, path, base_name): + """Add a raster layer given a raster layer file name + + :param path: Path to layer. + :type path: str + + :param base_name: Base name for layer. + :type base_name: str + """ + pass + + def activeLayer(self): + """Get pointer to the active layer (layer selected in the legend).""" + # noinspection PyArgumentList + layers = QgsMapLayerRegistry.instance().mapLayers() + for item in layers: + return layers[item] + + def addToolBarIcon(self, action): + """Add an icon to the plugins toolbar. + + :param action: Action to add to the toolbar. + :type action: QAction + """ + pass + + def removeToolBarIcon(self, action): + """Remove an action (icon) from the plugin toolbar. + + :param action: Action to add to the toolbar. + :type action: QAction + """ + pass + + def addToolBar(self, name): + """Add toolbar with specified name. + + :param name: Name for the toolbar. + :type name: str + """ + pass + + def mapCanvas(self): + """Return a pointer to the map canvas.""" + return self.canvas + + def mainWindow(self): + """Return a pointer to the main window. + + In case of QGIS it returns an instance of QgisApp. + """ + pass + + def addDockWidget(self, area, dock_widget): + """Add a dock widget to the main window. + + :param area: Where in the ui the dock should be placed. + :type area: + + :param dock_widget: A dock widget to add to the UI. + :type dock_widget: QDockWidget + """ + pass + + def legendInterface(self): + """Get the legend.""" + return self.canvas diff --git a/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.asc b/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.asc new file mode 100644 index 0000000..96a0ee1 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.asc @@ -0,0 +1,19 @@ +NCOLS 10 +NROWS 10 +XLLCENTER 1535380.000000 +YLLCENTER 5083260.000000 +DX 10 +DY 10 +NODATA_VALUE -9999 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +CRS +NOTES diff --git a/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.asc.aux.xml b/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.asc.aux.xml new file mode 100644 index 0000000..cfb1578 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.asc.aux.xml @@ -0,0 +1,13 @@ + + + Point + + + + 9 + 4.5 + 0 + 2.872281323269 + + + diff --git a/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.keywords b/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.keywords new file mode 100644 index 0000000..8be3f61 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.keywords @@ -0,0 +1 @@ +title: Tenbytenraster diff --git a/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.lic b/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.lic new file mode 100644 index 0000000..8345533 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.lic @@ -0,0 +1,18 @@ + + + + Tim Sutton, Linfiniti Consulting CC + + + + tenbytenraster.asc + 2700044251 + Yes + Tim Sutton + Tim Sutton (QGIS Source Tree) + Tim Sutton + This data is publicly available from QGIS Source Tree. The original + file was created and contributed to QGIS by Tim Sutton. + + + diff --git a/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.prj b/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.prj new file mode 100644 index 0000000..a30c00a --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.qml b/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.qml new file mode 100644 index 0000000..85247d4 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/tenbytenraster.qml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/QGIS_plugin/ecmwf_gridpoints/test/test_ecmwf_basins_dialog.py b/QGIS_plugin/ecmwf_gridpoints/test/test_ecmwf_basins_dialog.py new file mode 100644 index 0000000..f879360 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/test_ecmwf_basins_dialog.py @@ -0,0 +1,55 @@ +# coding=utf-8 +"""Dialog test. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +""" + +__author__ = 'metz@mundialis.de' +__date__ = '2019-08-26' +__copyright__ = 'Copyright 2019, mundialis' + +import unittest + +from qgis.PyQt.QtGui import QDialogButtonBox, QDialog + +from ecmwf_basins_dialog import BasinsDialog + +from utilities import get_qgis_app +QGIS_APP = get_qgis_app() + + +class BasinsDialogTest(unittest.TestCase): + """Test dialog works.""" + + def setUp(self): + """Runs before each test.""" + self.dialog = BasinsDialog(None) + + def tearDown(self): + """Runs after each test.""" + self.dialog = None + + def test_dialog_ok(self): + """Test we can click OK.""" + + button = self.dialog.button_box.button(QDialogButtonBox.Ok) + button.click() + result = self.dialog.result() + self.assertEqual(result, QDialog.Accepted) + + def test_dialog_cancel(self): + """Test we can click cancel.""" + button = self.dialog.button_box.button(QDialogButtonBox.Cancel) + button.click() + result = self.dialog.result() + self.assertEqual(result, QDialog.Rejected) + +if __name__ == "__main__": + suite = unittest.makeSuite(BasinsDialogTest) + runner = unittest.TextTestRunner(verbosity=2) + runner.run(suite) + diff --git a/QGIS_plugin/ecmwf_gridpoints/test/test_init.py b/QGIS_plugin/ecmwf_gridpoints/test/test_init.py new file mode 100644 index 0000000..a11ca44 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/test_init.py @@ -0,0 +1,64 @@ +# coding=utf-8 +"""Tests QGIS plugin init.""" + +__author__ = 'Tim Sutton ' +__revision__ = '$Format:%H$' +__date__ = '17/10/2010' +__license__ = "GPL" +__copyright__ = 'Copyright 2012, Australia Indonesia Facility for ' +__copyright__ += 'Disaster Reduction' + +import os +import unittest +import logging +import configparser + +LOGGER = logging.getLogger('QGIS') + + +class TestInit(unittest.TestCase): + """Test that the plugin init is usable for QGIS. + + Based heavily on the validator class by Alessandro + Passoti available here: + + http://github.com/qgis/qgis-django/blob/master/qgis-app/ + plugins/validator.py + + """ + + def test_read_init(self): + """Test that the plugin __init__ will validate on plugins.qgis.org.""" + + # You should update this list according to the latest in + # https://github.com/qgis/qgis-django/blob/master/qgis-app/ + # plugins/validator.py + + required_metadata = [ + 'name', + 'description', + 'version', + 'qgisMinimumVersion', + 'email', + 'author'] + + file_path = os.path.abspath(os.path.join( + os.path.dirname(__file__), os.pardir, + 'metadata.txt')) + LOGGER.info(file_path) + metadata = [] + parser = configparser.ConfigParser() + parser.optionxform = str + parser.read(file_path) + message = 'Cannot find a section named "general" in %s' % file_path + assert parser.has_section('general'), message + metadata.extend(parser.items('general')) + + for expectation in required_metadata: + message = ('Cannot find metadata "%s" in metadata source (%s).' % ( + expectation, file_path)) + + self.assertIn(expectation, dict(metadata), message) + +if __name__ == '__main__': + unittest.main() diff --git a/QGIS_plugin/ecmwf_gridpoints/test/test_qgis_environment.py b/QGIS_plugin/ecmwf_gridpoints/test/test_qgis_environment.py new file mode 100644 index 0000000..1becb30 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/test_qgis_environment.py @@ -0,0 +1,60 @@ +# coding=utf-8 +"""Tests for QGIS functionality. + + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +""" +__author__ = 'tim@linfiniti.com' +__date__ = '20/01/2011' +__copyright__ = ('Copyright 2012, Australia Indonesia Facility for ' + 'Disaster Reduction') + +import os +import unittest +from qgis.core import ( + QgsProviderRegistry, + QgsCoordinateReferenceSystem, + QgsRasterLayer) + +from .utilities import get_qgis_app +QGIS_APP = get_qgis_app() + + +class QGISTest(unittest.TestCase): + """Test the QGIS Environment""" + + def test_qgis_environment(self): + """QGIS environment has the expected providers""" + + r = QgsProviderRegistry.instance() + self.assertIn('gdal', r.providerList()) + self.assertIn('ogr', r.providerList()) + self.assertIn('postgres', r.providerList()) + + def test_projection(self): + """Test that QGIS properly parses a wkt string. + """ + crs = QgsCoordinateReferenceSystem() + wkt = ( + 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",' + 'SPHEROID["WGS_1984",6378137.0,298.257223563]],' + 'PRIMEM["Greenwich",0.0],UNIT["Degree",' + '0.0174532925199433]]') + crs.createFromWkt(wkt) + auth_id = crs.authid() + expected_auth_id = 'EPSG:4326' + self.assertEqual(auth_id, expected_auth_id) + + # now test for a loaded layer + path = os.path.join(os.path.dirname(__file__), 'tenbytenraster.asc') + title = 'TestRaster' + layer = QgsRasterLayer(path, title) + auth_id = layer.crs().authid() + self.assertEqual(auth_id, expected_auth_id) + +if __name__ == '__main__': + unittest.main() diff --git a/QGIS_plugin/ecmwf_gridpoints/test/test_resources.py b/QGIS_plugin/ecmwf_gridpoints/test/test_resources.py new file mode 100644 index 0000000..2969d06 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/test_resources.py @@ -0,0 +1,44 @@ +# coding=utf-8 +"""Resources test. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +""" + +__author__ = 'metz@mundialis.de' +__date__ = '2019-08-26' +__copyright__ = 'Copyright 2019, mundialis' + +import unittest + +from qgis.PyQt.QtGui import QIcon + + + +class BasinsDialogTest(unittest.TestCase): + """Test rerources work.""" + + def setUp(self): + """Runs before each test.""" + pass + + def tearDown(self): + """Runs after each test.""" + pass + + def test_icon_png(self): + """Test we can click OK.""" + path = ':/plugins/Basins/icon.png' + icon = QIcon(path) + self.assertFalse(icon.isNull()) + +if __name__ == "__main__": + suite = unittest.makeSuite(BasinsResourcesTest) + runner = unittest.TextTestRunner(verbosity=2) + runner.run(suite) + + + diff --git a/QGIS_plugin/ecmwf_gridpoints/test/test_translations.py b/QGIS_plugin/ecmwf_gridpoints/test/test_translations.py new file mode 100644 index 0000000..035dc62 --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/test_translations.py @@ -0,0 +1,55 @@ +# coding=utf-8 +"""Safe Translations Test. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +""" +from .utilities import get_qgis_app + +__author__ = 'ismailsunni@yahoo.co.id' +__date__ = '12/10/2011' +__copyright__ = ('Copyright 2012, Australia Indonesia Facility for ' + 'Disaster Reduction') +import unittest +import os + +from qgis.PyQt.QtCore import QCoreApplication, QTranslator + +QGIS_APP = get_qgis_app() + + +class SafeTranslationsTest(unittest.TestCase): + """Test translations work.""" + + def setUp(self): + """Runs before each test.""" + if 'LANG' in iter(os.environ.keys()): + os.environ.__delitem__('LANG') + + def tearDown(self): + """Runs after each test.""" + if 'LANG' in iter(os.environ.keys()): + os.environ.__delitem__('LANG') + + def test_qgis_translations(self): + """Test that translations work.""" + parent_path = os.path.join(__file__, os.path.pardir, os.path.pardir) + dir_path = os.path.abspath(parent_path) + file_path = os.path.join( + dir_path, 'i18n', 'af.qm') + translator = QTranslator() + translator.load(file_path) + QCoreApplication.installTranslator(translator) + + expected_message = 'Goeie more' + real_message = QCoreApplication.translate("@default", 'Good morning') + self.assertEqual(real_message, expected_message) + + +if __name__ == "__main__": + suite = unittest.makeSuite(SafeTranslationsTest) + runner = unittest.TextTestRunner(verbosity=2) + runner.run(suite) diff --git a/QGIS_plugin/ecmwf_gridpoints/test/utilities.py b/QGIS_plugin/ecmwf_gridpoints/test/utilities.py new file mode 100644 index 0000000..be7ee3b --- /dev/null +++ b/QGIS_plugin/ecmwf_gridpoints/test/utilities.py @@ -0,0 +1,61 @@ +# coding=utf-8 +"""Common functionality used by regression tests.""" + +import sys +import logging + + +LOGGER = logging.getLogger('QGIS') +QGIS_APP = None # Static variable used to hold hand to running QGIS app +CANVAS = None +PARENT = None +IFACE = None + + +def get_qgis_app(): + """ Start one QGIS application to test against. + + :returns: Handle to QGIS app, canvas, iface and parent. If there are any + errors the tuple members will be returned as None. + :rtype: (QgsApplication, CANVAS, IFACE, PARENT) + + If QGIS is already running the handle to that app will be returned. + """ + + try: + from qgis.PyQt import QtGui, QtCore + from qgis.core import QgsApplication + from qgis.gui import QgsMapCanvas + from .qgis_interface import QgisInterface + except ImportError: + return None, None, None, None + + global QGIS_APP # pylint: disable=W0603 + + if QGIS_APP is None: + gui_flag = True # All test will run qgis in gui mode + #noinspection PyPep8Naming + QGIS_APP = QgsApplication(sys.argv, gui_flag) + # Make sure QGIS_PREFIX_PATH is set in your env if needed! + QGIS_APP.initQgis() + s = QGIS_APP.showSettings() + LOGGER.debug(s) + + global PARENT # pylint: disable=W0603 + if PARENT is None: + #noinspection PyPep8Naming + PARENT = QtGui.QWidget() + + global CANVAS # pylint: disable=W0603 + if CANVAS is None: + #noinspection PyPep8Naming + CANVAS = QgsMapCanvas(PARENT) + CANVAS.resize(QtCore.QSize(400, 400)) + + global IFACE # pylint: disable=W0603 + if IFACE is None: + # QgisInterface is a stub implementation of the QGIS plugin interface + #noinspection PyPep8Naming + IFACE = QgisInterface(CANVAS) + + return QGIS_APP, CANVAS, IFACE, PARENT From 9db728c2a168e97d122d588f45157c1ad9f092d1 Mon Sep 17 00:00:00 2001 From: Markus Metz Date: Wed, 28 Aug 2019 14:29:35 +0200 Subject: [PATCH 04/12] ecmwf_basins new QGIS plugin to update river basins --- QGIS_plugin/ecmwf_basins/Makefile | 244 ++++++++++++++ QGIS_plugin/ecmwf_basins/README.html | 42 +++ QGIS_plugin/ecmwf_basins/README.txt | 32 ++ QGIS_plugin/ecmwf_basins/__init__.py | 36 +++ QGIS_plugin/ecmwf_basins/ecmwf_basins.py | 306 ++++++++++++++++++ .../ecmwf_basins/ecmwf_basins_dialog.py | 44 +++ .../ecmwf_basins/ecmwf_basins_dialog_base.ui | 238 ++++++++++++++ QGIS_plugin/ecmwf_basins/help/Makefile | 130 ++++++++ QGIS_plugin/ecmwf_basins/help/make.bat | 155 +++++++++ QGIS_plugin/ecmwf_basins/help/source/conf.py | 216 +++++++++++++ .../ecmwf_basins/help/source/index.rst | 20 ++ QGIS_plugin/ecmwf_basins/i18n/af.ts | 11 + QGIS_plugin/ecmwf_basins/icon.png | Bin 0 -> 1034 bytes QGIS_plugin/ecmwf_basins/metadata.txt | 47 +++ QGIS_plugin/ecmwf_basins/pb_tool.cfg | 80 +++++ QGIS_plugin/ecmwf_basins/plugin_upload.py | 111 +++++++ QGIS_plugin/ecmwf_basins/pylintrc | 281 ++++++++++++++++ QGIS_plugin/ecmwf_basins/resources.py | 128 ++++++++ QGIS_plugin/ecmwf_basins/resources.qrc | 5 + .../ecmwf_basins/scripts/compile-strings.sh | 12 + .../ecmwf_basins/scripts/run-env-linux.sh | 28 ++ .../ecmwf_basins/scripts/update-strings.sh | 56 ++++ QGIS_plugin/ecmwf_basins/test/__init__.py | 2 + .../ecmwf_basins/test/qgis_interface.py | 205 ++++++++++++ .../ecmwf_basins/test/tenbytenraster.asc | 19 ++ .../test/tenbytenraster.asc.aux.xml | 13 + .../ecmwf_basins/test/tenbytenraster.keywords | 1 + .../ecmwf_basins/test/tenbytenraster.lic | 18 ++ .../ecmwf_basins/test/tenbytenraster.prj | 1 + .../ecmwf_basins/test/tenbytenraster.qml | 26 ++ .../test/test_ecmwf_basins_dialog.py | 55 ++++ QGIS_plugin/ecmwf_basins/test/test_init.py | 64 ++++ .../test/test_qgis_environment.py | 60 ++++ .../ecmwf_basins/test/test_resources.py | 44 +++ .../ecmwf_basins/test/test_translations.py | 55 ++++ QGIS_plugin/ecmwf_basins/test/utilities.py | 61 ++++ 36 files changed, 2846 insertions(+) create mode 100644 QGIS_plugin/ecmwf_basins/Makefile create mode 100644 QGIS_plugin/ecmwf_basins/README.html create mode 100644 QGIS_plugin/ecmwf_basins/README.txt create mode 100644 QGIS_plugin/ecmwf_basins/__init__.py create mode 100644 QGIS_plugin/ecmwf_basins/ecmwf_basins.py create mode 100644 QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog.py create mode 100644 QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog_base.ui create mode 100644 QGIS_plugin/ecmwf_basins/help/Makefile create mode 100644 QGIS_plugin/ecmwf_basins/help/make.bat create mode 100644 QGIS_plugin/ecmwf_basins/help/source/conf.py create mode 100644 QGIS_plugin/ecmwf_basins/help/source/index.rst create mode 100644 QGIS_plugin/ecmwf_basins/i18n/af.ts create mode 100644 QGIS_plugin/ecmwf_basins/icon.png create mode 100644 QGIS_plugin/ecmwf_basins/metadata.txt create mode 100644 QGIS_plugin/ecmwf_basins/pb_tool.cfg create mode 100755 QGIS_plugin/ecmwf_basins/plugin_upload.py create mode 100644 QGIS_plugin/ecmwf_basins/pylintrc create mode 100644 QGIS_plugin/ecmwf_basins/resources.py create mode 100644 QGIS_plugin/ecmwf_basins/resources.qrc create mode 100644 QGIS_plugin/ecmwf_basins/scripts/compile-strings.sh create mode 100644 QGIS_plugin/ecmwf_basins/scripts/run-env-linux.sh create mode 100644 QGIS_plugin/ecmwf_basins/scripts/update-strings.sh create mode 100644 QGIS_plugin/ecmwf_basins/test/__init__.py create mode 100644 QGIS_plugin/ecmwf_basins/test/qgis_interface.py create mode 100644 QGIS_plugin/ecmwf_basins/test/tenbytenraster.asc create mode 100644 QGIS_plugin/ecmwf_basins/test/tenbytenraster.asc.aux.xml create mode 100644 QGIS_plugin/ecmwf_basins/test/tenbytenraster.keywords create mode 100644 QGIS_plugin/ecmwf_basins/test/tenbytenraster.lic create mode 100644 QGIS_plugin/ecmwf_basins/test/tenbytenraster.prj create mode 100644 QGIS_plugin/ecmwf_basins/test/tenbytenraster.qml create mode 100644 QGIS_plugin/ecmwf_basins/test/test_ecmwf_basins_dialog.py create mode 100644 QGIS_plugin/ecmwf_basins/test/test_init.py create mode 100644 QGIS_plugin/ecmwf_basins/test/test_qgis_environment.py create mode 100644 QGIS_plugin/ecmwf_basins/test/test_resources.py create mode 100644 QGIS_plugin/ecmwf_basins/test/test_translations.py create mode 100644 QGIS_plugin/ecmwf_basins/test/utilities.py diff --git a/QGIS_plugin/ecmwf_basins/Makefile b/QGIS_plugin/ecmwf_basins/Makefile new file mode 100644 index 0000000..ec0ee74 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/Makefile @@ -0,0 +1,244 @@ +#/*************************************************************************** +# Basins +# +# This plugin updates ECMWF basins +# ------------------- +# begin : 2019-08-26 +# git sha : $Format:%H$ +# copyright : (C) 2019 by mundialis +# email : metz@mundialis.de +# ***************************************************************************/ +# +#/*************************************************************************** +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU General Public License as published by * +# * the Free Software Foundation; either version 2 of the License, or * +# * (at your option) any later version. * +# * * +# ***************************************************************************/ + +################################################# +# Edit the following to match your sources lists +################################################# + + +#Add iso code for any locales you want to support here (space separated) +# default is no locales +# LOCALES = af +LOCALES = + +# If locales are enabled, set the name of the lrelease binary on your system. If +# you have trouble compiling the translations, you may have to specify the full path to +# lrelease +#LRELEASE = lrelease +#LRELEASE = lrelease-qt4 + + +# translation +SOURCES = \ + __init__.py \ + ecmwf_basins.py ecmwf_basins_dialog.py + +PLUGINNAME = ecmwf_basins + +PY_FILES = \ + __init__.py \ + ecmwf_basins.py ecmwf_basins_dialog.py + +UI_FILES = ecmwf_basins_dialog_base.ui + +EXTRAS = metadata.txt icon.png + +EXTRA_DIRS = + +COMPILED_RESOURCE_FILES = resources.py + +PEP8EXCLUDE=pydev,resources.py,conf.py,third_party,ui + +# QGISDIR points to the location where your plugin should be installed. +# This varies by platform, relative to your HOME directory: +# * Linux: +# .local/share/QGIS/QGIS3/profiles/default/python/plugins/ +# * Mac OS X: +# Library/Application Support/QGIS/QGIS3/profiles/default/python/plugins +# * Windows: +# AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins' + +QGISDIR=/home/metz/.local/share/QGIS/QGIS3/profiles/default/python/plugins/ + +################################################# +# Normally you would not need to edit below here +################################################# + +HELP = help/build/html + +PLUGIN_UPLOAD = $(c)/plugin_upload.py + +RESOURCE_SRC=$(shell grep '^ *@@g;s/.*>//g' | tr '\n' ' ') + +.PHONY: default +default: + @echo While you can use make to build and deploy your plugin, pb_tool + @echo is a much better solution. + @echo A Python script, pb_tool provides platform independent management of + @echo your plugins and runs anywhere. + @echo You can install pb_tool using: pip install pb_tool + @echo See https://g-sherman.github.io/plugin_build_tool/ for info. + +compile: $(COMPILED_RESOURCE_FILES) + +%.py : %.qrc $(RESOURCES_SRC) + pyrcc5 -o $*.py $< + +%.qm : %.ts + $(LRELEASE) $< + +test: compile transcompile + @echo + @echo "----------------------" + @echo "Regression Test Suite" + @echo "----------------------" + + @# Preceding dash means that make will continue in case of errors + @-export PYTHONPATH=`pwd`:$(PYTHONPATH); \ + export QGIS_DEBUG=0; \ + export QGIS_LOG_FILE=/dev/null; \ + nosetests -v --with-id --with-coverage --cover-package=. \ + 3>&1 1>&2 2>&3 3>&- || true + @echo "----------------------" + @echo "If you get a 'no module named qgis.core error, try sourcing" + @echo "the helper script we have provided first then run make test." + @echo "e.g. source run-env-linux.sh ; make test" + @echo "----------------------" + +deploy: compile doc transcompile + @echo + @echo "------------------------------------------" + @echo "Deploying plugin to your .qgis2 directory." + @echo "------------------------------------------" + # The deploy target only works on unix like operating system where + # the Python plugin directory is located at: + # $HOME/$(QGISDIR)/python/plugins + mkdir -p $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(PY_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(UI_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(COMPILED_RESOURCE_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(EXTRAS) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vfr i18n $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vfr $(HELP) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)/help + # Copy extra directories if any + (foreach EXTRA_DIR,(EXTRA_DIRS), cp -R (EXTRA_DIR) (HOME)/(QGISDIR)/python/plugins/(PLUGINNAME)/;) + + +# The dclean target removes compiled python files from plugin directory +# also deletes any .git entry +dclean: + @echo + @echo "-----------------------------------" + @echo "Removing any compiled python files." + @echo "-----------------------------------" + find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname "*.pyc" -delete + find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname ".git" -prune -exec rm -Rf {} \; + + +derase: + @echo + @echo "-------------------------" + @echo "Removing deployed plugin." + @echo "-------------------------" + rm -Rf $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + +zip: deploy dclean + @echo + @echo "---------------------------" + @echo "Creating plugin zip bundle." + @echo "---------------------------" + # The zip target deploys the plugin and creates a zip file with the deployed + # content. You can then upload the zip file on http://plugins.qgis.org + rm -f $(PLUGINNAME).zip + cd $(HOME)/$(QGISDIR)/python/plugins; zip -9r $(CURDIR)/$(PLUGINNAME).zip $(PLUGINNAME) + +package: compile + # Create a zip package of the plugin named $(PLUGINNAME).zip. + # This requires use of git (your plugin development directory must be a + # git repository). + # To use, pass a valid commit or tag as follows: + # make package VERSION=Version_0.3.2 + @echo + @echo "------------------------------------" + @echo "Exporting plugin to zip package. " + @echo "------------------------------------" + rm -f $(PLUGINNAME).zip + git archive --prefix=$(PLUGINNAME)/ -o $(PLUGINNAME).zip $(VERSION) + echo "Created package: $(PLUGINNAME).zip" + +upload: zip + @echo + @echo "-------------------------------------" + @echo "Uploading plugin to QGIS Plugin repo." + @echo "-------------------------------------" + $(PLUGIN_UPLOAD) $(PLUGINNAME).zip + +transup: + @echo + @echo "------------------------------------------------" + @echo "Updating translation files with any new strings." + @echo "------------------------------------------------" + @chmod +x scripts/update-strings.sh + @scripts/update-strings.sh $(LOCALES) + +transcompile: + @echo + @echo "----------------------------------------" + @echo "Compiled translation files to .qm files." + @echo "----------------------------------------" + @chmod +x scripts/compile-strings.sh + @scripts/compile-strings.sh $(LRELEASE) $(LOCALES) + +transclean: + @echo + @echo "------------------------------------" + @echo "Removing compiled translation files." + @echo "------------------------------------" + rm -f i18n/*.qm + +clean: + @echo + @echo "------------------------------------" + @echo "Removing uic and rcc generated files" + @echo "------------------------------------" + rm $(COMPILED_UI_FILES) $(COMPILED_RESOURCE_FILES) + +doc: + @echo + @echo "------------------------------------" + @echo "Building documentation using sphinx." + @echo "------------------------------------" + cd help; make html + +pylint: + @echo + @echo "-----------------" + @echo "Pylint violations" + @echo "-----------------" + @pylint --reports=n --rcfile=pylintrc . || true + @echo + @echo "----------------------" + @echo "If you get a 'no module named qgis.core' error, try sourcing" + @echo "the helper script we have provided first then run make pylint." + @echo "e.g. source run-env-linux.sh ; make pylint" + @echo "----------------------" + + +# Run pep8 style checking +#http://pypi.python.org/pypi/pep8 +pep8: + @echo + @echo "-----------" + @echo "PEP8 issues" + @echo "-----------" + @pep8 --repeat --ignore=E203,E121,E122,E123,E124,E125,E126,E127,E128 --exclude $(PEP8EXCLUDE) . || true + @echo "-----------" + @echo "Ignored in PEP8 check:" + @echo $(PEP8EXCLUDE) diff --git a/QGIS_plugin/ecmwf_basins/README.html b/QGIS_plugin/ecmwf_basins/README.html new file mode 100644 index 0000000..3909b33 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/README.html @@ -0,0 +1,42 @@ + + +

Plugin Builder Results

+ +Congratulations! You just built a plugin for QGIS!

+ +
+Your plugin ecmwf basins was created in:
+  /media/storage/Linux/apps/GRASS/ESoWC_challenge_2019/github_sample_data/gis_dissolve_tool/QGIS_plugin/ecmwf_basins +

+Your QGIS plugin directory is located at:
+  /home/metz/.local/share/QGIS/QGIS3/profiles/default/python/plugins +

+

What's Next

+
    +
  1. If resources.py is not present in your plugin directory, compile the resources file using pyrcc5 (simply use pb_tool or make if you have automake) +
  2. Optionally, test the generated sources using make test (or run tests from your IDE) +
  3. Copy the entire directory containing your new plugin to the QGIS plugin directory (see Notes below) +
  4. Test the plugin by enabling it in the QGIS plugin manager +
  5. Customize it by editing the implementation file ecmwf_basins.py +
  6. Create your own custom icon, replacing the default icon.png +
  7. Modify your user interface by opening ecmwf_basins_dialog_base.ui in Qt Designer +
+Notes: +
    +
  • You can use pb_tool to compile, deploy, and manage your plugin. Tweak the pb_tool.cfg file included with your plugin as you add files. Install pb_tool using + pip or easy_install. See http://loc8.cc/pb_tool for more information. +
  • You can also use the Makefile to compile and deploy when you + make changes. This requires GNU make (gmake). The Makefile is ready to use, however you + will have to edit it to add addional Python source files, dialogs, and translations. +
+
+
+

+For information on writing PyQGIS code, see http://loc8.cc/pyqgis_resources for a list of resources. +

+
+

+©2011-2019 GeoApt LLC - geoapt.com +

+ + diff --git a/QGIS_plugin/ecmwf_basins/README.txt b/QGIS_plugin/ecmwf_basins/README.txt new file mode 100644 index 0000000..327960a --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/README.txt @@ -0,0 +1,32 @@ +Plugin Builder Results + +Your plugin Update basins was created in: + /media/storage/Linux/apps/GRASS/ESoWC_challenge_2019/github_sample_data/gis_dissolve_tool/QGIS_plugin/ecmwf_basins + +Your QGIS plugin directory is located at: + /home/metz/.local/share/QGIS/QGIS3/profiles/default/python/plugins + +What's Next: + + * Copy the entire directory containing your new plugin to the QGIS plugin + directory + + * Compile the resources file using pyrcc5 + + * Run the tests (``make test``) + + * Test the plugin by enabling it in the QGIS plugin manager + + * Customize it by editing the implementation file: ``ecmwf_basins.py`` + + * Create your own custom icon, replacing the default icon.png + + * Modify your user interface by opening ecmwf_basins_dialog_base.ui in Qt Designer + + * You can use the Makefile to compile your Ui and resource files when + you make changes. This requires GNU make (gmake) + +For more information, see the PyQGIS Developer Cookbook at: +http://www.qgis.org/pyqgis-cookbook/index.html + +(C) 2011-2018 GeoApt LLC - geoapt.com diff --git a/QGIS_plugin/ecmwf_basins/__init__.py b/QGIS_plugin/ecmwf_basins/__init__.py new file mode 100644 index 0000000..343ec2a --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + Basins + A QGIS plugin + This plugin updates ECMWF basins + Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ + ------------------- + begin : 2019-08-26 + copyright : (C) 2019 by mundialis + email : metz@mundialis.de + git sha : $Format:%H$ + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + This script initializes the plugin, making it known to QGIS. +""" + + +# noinspection PyPep8Naming +def classFactory(iface): # pylint: disable=invalid-name + """Load Basins class from file Basins. + + :param iface: A QGIS interface instance. + :type iface: QgsInterface + """ + # + from .ecmwf_basins import Basins + return Basins(iface) diff --git a/QGIS_plugin/ecmwf_basins/ecmwf_basins.py b/QGIS_plugin/ecmwf_basins/ecmwf_basins.py new file mode 100644 index 0000000..c524113 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/ecmwf_basins.py @@ -0,0 +1,306 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + Basins + A QGIS plugin + This plugin updates ECMWF basins + Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ + ------------------- + begin : 2019-08-26 + git sha : $Format:%H$ + copyright : (C) 2019 by mundialis + email : metz@mundialis.de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" +from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtWidgets import QAction, QFileDialog +from qgis.core import QgsProject, Qgis, QgsProcessingFeedback + +# Initialize Qt resources from file resources.py +from .resources import * +# Import the code for the dialog +from .ecmwf_basins_dialog import BasinsDialog +import os.path + +import processing +from processing.algs.grass7.Grass7Utils import Grass7Utils + +class Basins: + """QGIS Plugin Implementation.""" + + def __init__(self, iface): + """Constructor. + + :param iface: An interface instance that will be passed to this class + which provides the hook by which you can manipulate the QGIS + application at run time. + :type iface: QgsInterface + """ + # Save reference to the QGIS interface + self.iface = iface + # initialize plugin directory + self.plugin_dir = os.path.dirname(__file__) + # initialize locale + locale = QSettings().value('locale/userLocale')[0:2] + locale_path = os.path.join( + self.plugin_dir, + 'i18n', + 'Basins_{}.qm'.format(locale)) + + if os.path.exists(locale_path): + self.translator = QTranslator() + self.translator.load(locale_path) + QCoreApplication.installTranslator(self.translator) + + # Declare instance attributes + self.actions = [] + self.menu = self.tr(u'&ECMWF') + + # Check if plugin was started the first time in current QGIS session + # Must be set in initGui() to survive plugin reloads + self.first_start = None + + # noinspection PyMethodMayBeStatic + def tr(self, message): + """Get the translation for a string using Qt translation API. + + We implement this ourselves since we do not inherit QObject. + + :param message: String for translation. + :type message: str, QString + + :returns: Translated version of message. + :rtype: QString + """ + # noinspection PyTypeChecker,PyArgumentList,PyCallByClass + return QCoreApplication.translate('Basins', message) + + + def add_action( + self, + icon_path, + text, + callback, + enabled_flag=True, + add_to_menu=True, + add_to_toolbar=True, + status_tip=None, + whats_this=None, + parent=None): + """Add a toolbar icon to the toolbar. + + :param icon_path: Path to the icon for this action. Can be a resource + path (e.g. ':/plugins/foo/bar.png') or a normal file system path. + :type icon_path: str + + :param text: Text that should be shown in menu items for this action. + :type text: str + + :param callback: Function to be called when the action is triggered. + :type callback: function + + :param enabled_flag: A flag indicating if the action should be enabled + by default. Defaults to True. + :type enabled_flag: bool + + :param add_to_menu: Flag indicating whether the action should also + be added to the menu. Defaults to True. + :type add_to_menu: bool + + :param add_to_toolbar: Flag indicating whether the action should also + be added to the toolbar. Defaults to True. + :type add_to_toolbar: bool + + :param status_tip: Optional text to show in a popup when mouse pointer + hovers over the action. + :type status_tip: str + + :param parent: Parent widget for the new action. Defaults None. + :type parent: QWidget + + :param whats_this: Optional text to show in the status bar when the + mouse pointer hovers over the action. + + :returns: The action that was created. Note that the action is also + added to self.actions list. + :rtype: QAction + """ + + icon = QIcon(icon_path) + action = QAction(icon, text, parent) + action.triggered.connect(callback) + action.setEnabled(enabled_flag) + + if status_tip is not None: + action.setStatusTip(status_tip) + + if whats_this is not None: + action.setWhatsThis(whats_this) + + if add_to_toolbar: + # Adds plugin icon to Plugins toolbar + self.iface.addToolBarIcon(action) + + if add_to_menu: + self.iface.addPluginToMenu( + self.menu, + action) + + self.actions.append(action) + + return action + + def initGui(self): + """Create the menu entries and toolbar icons inside the QGIS GUI.""" + + icon_path = ':/plugins/ecmwf_basins/icon.png' + self.add_action( + icon_path, + text=self.tr(u'Update basins'), + callback=self.run, + parent=self.iface.mainWindow()) + + # will be set False in run() + self.first_start = True + + + def unload(self): + """Removes the plugin menu item and icon from QGIS GUI.""" + for action in self.actions: + self.iface.removePluginMenu( + self.tr(u'&ECMWF'), + action) + self.iface.removeToolBarIcon(action) + + + def select_column_ID(self): + colname = "MOU_IDS" + self.dlg.lineEdit_4.setText(colname) + + + def select_column_all_IDs(self): + colname = "MOUids_all" + self.dlg.lineEdit_5.setText(colname) + + + def select_output_vector(self): + filename, _filter = QFileDialog.getSaveFileName( + self.dlg, "Select output vector ","", '*.shp') + self.dlg.lineEdit.setText(filename) + + + def select_grass_addon(self): + dirname = QFileDialog.getExistingDirectory( + self.dlg, "Select directory ","/home", QFileDialog.ShowDirsOnly) + self.dlg.lineEdit_3.setText(dirname) + + + def run(self): + """Run method that performs all the real work""" + + # Create the dialog with elements (after translation) and keep reference + # Only create GUI ONCE in callback, so that it will only load when the plugin is started + if self.first_start == True: + self.first_start = False + self.dlg = BasinsDialog() + self.dlg.pushButton.clicked.connect(self.select_output_vector) + self.dlg.pushButton_3.clicked.connect(self.select_grass_addon) + + + # Fetch the currently loaded layers + layers = QgsProject.instance().layerTreeRoot().children() + + # Clear the contents of the comboBox from previous runs + self.dlg.comboBox.clear() + # Populate the comboBox with names of all the loaded layers + self.dlg.comboBox.addItems([layer.name() for layer in layers]) + + # Clear the contents of the comboBox from previous runs + self.dlg.comboBox_3.clear() + # Populate the comboBox with names of all the loaded layers + self.dlg.comboBox_3.addItems([layer.name() for layer in layers]) + + val = self.dlg.lineEdit_4.text() + if val is None or len(val) == 0: + self.dlg.lineEdit_4.setText("MOU_IDS") + val = self.dlg.lineEdit_5.text() + if val is None or len(val) == 0: + self.dlg.lineEdit_5.setText("MOUids_all") + + # show the dialog + self.dlg.show() + # Run the dialog event loop + result = self.dlg.exec_() + # See if OK was pressed + if result: + # Do something useful here - delete the line containing pass and + # substitute with your code. + + feedback = QgsProcessingFeedback() + + # partner regions + selectedLayerIndex = self.dlg.comboBox.currentIndex() + partner_regions = layers[selectedLayerIndex].layer().dataProvider().dataSourceUri() + partner_regions = partner_regions[:partner_regions.rfind('|')] + + # basins + selectedLayerIndex = self.dlg.comboBox_3.currentIndex() + basins = layers[selectedLayerIndex].layer().dataProvider().dataSourceUri() + basins = basins[:basins.rfind('|')] + + # debug only + """ + self.iface.messageBar().pushMessage( + "Info", "Grid points to update is in " + grid_points, + level=Qgis.Info, duration=3) + """ + + outputvector = self.dlg.lineEdit.text() + pidcol = self.dlg.lineEdit_4.text() + allpidcol = self.dlg.lineEdit_5.text() + + grasscommand = Grass7Utils.grassBin() + if not grasscommand: + self.iface.messageBar().pushMessage( + "Error", "GRASS GIS not found", + level=Qgis.Critical) + return + + Grass7Utils.startGrassSession() + + # install the GRASS addon v.ecmwf.riverbasins + grassaddonpath = self.dlg.lineEdit_3.text() + if grassaddonpath: + gcommands = ("g.extension extension=v.ecmwf.riverbasins url='%s'" % grassaddonpath) + Grass7Utils.executeGrass([gcommands], feedback) + + gcommands = ("v.ecmwf.riverbasins partner_regions=\"%s\" basins=\"%s\" output=\"%s\"" % + (partner_regions, basins, outputvector)) + + if pidcol: + gcommands = gcommands + (" partner_id=%s " % pidcol) + + if allpidcol: + gcommands = gcommands + (" all_partner_id=%s " % allpidcol) + + # debug only + print("GRASS command:") + print("%s" % gcommands) + + Grass7Utils.executeGrass([gcommands], feedback) + + Grass7Utils.endGrassSession() + + self.iface.messageBar().pushMessage( + "Success", "Output vector written at " + outputvector, + level=Qgis.Success, duration=3) diff --git a/QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog.py b/QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog.py new file mode 100644 index 0000000..fa1590a --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + BasinsDialog + A QGIS plugin + This plugin updates ECMWF basins + Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ + ------------------- + begin : 2019-08-26 + git sha : $Format:%H$ + copyright : (C) 2019 by mundialis + email : metz@mundialis.de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +import os + +from qgis.PyQt import uic +from qgis.PyQt import QtWidgets + +# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer +FORM_CLASS, _ = uic.loadUiType(os.path.join( + os.path.dirname(__file__), 'ecmwf_basins_dialog_base.ui')) + + +class BasinsDialog(QtWidgets.QDialog, FORM_CLASS): + def __init__(self, parent=None): + """Constructor.""" + super(BasinsDialog, self).__init__(parent) + # Set up the user interface from Designer through FORM_CLASS. + # After self.setupUi() you can access any designer object by doing + # self., and you can use autoconnect slots - see + # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html + # #widgets-and-dialogs-with-auto-connect + self.setupUi(self) diff --git a/QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog_base.ui b/QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog_base.ui new file mode 100644 index 0000000..ba6625a --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog_base.ui @@ -0,0 +1,238 @@ + + + BasinsDialogBase + + + + 0 + 0 + 623 + 499 + + + + ECMWF grid points + + + + + 260 + 450 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 340 + 30 + 231 + 27 + + + + + + + 30 + 30 + 271 + 19 + + + + Partner regions + + + + + + 30 + 250 + 271 + 19 + + + + Updated river basins + + + + + + 340 + 250 + 181 + 27 + + + + + + + 540 + 250 + 31 + 27 + + + + ... + + + + + + 30 + 300 + 281 + 19 + + + + Folder with v.ecmwf.riverbasins + + + + + + 340 + 300 + 181 + 27 + + + + + + + 540 + 300 + 31 + 27 + + + + ... + + + + + + 30 + 140 + 221 + 19 + + + + <html><head/><body><p>default: MOU_IDS</p></body></html> + + + Column with partner ID + + + + + + 30 + 190 + 271 + 19 + + + + <html><head/><body><p>default: MOUids_all</p></body></html> + + + Column with all partner IDs + + + + + + 410 + 140 + 161 + 27 + + + + + + + 410 + 190 + 161 + 27 + + + + + + + 30 + 80 + 241 + 16 + + + + River basins + + + + + + 340 + 80 + 231 + 23 + + + + + + + + button_box + accepted() + BasinsDialogBase + accept() + + + 20 + 20 + + + 20 + 20 + + + + + button_box + rejected() + BasinsDialogBase + reject() + + + 20 + 20 + + + 20 + 20 + + + + + diff --git a/QGIS_plugin/ecmwf_basins/help/Makefile b/QGIS_plugin/ecmwf_basins/help/Makefile new file mode 100644 index 0000000..9def777 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/help/Makefile @@ -0,0 +1,130 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/template_class.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/template_class.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/template_class" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/template_class" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/QGIS_plugin/ecmwf_basins/help/make.bat b/QGIS_plugin/ecmwf_basins/help/make.bat new file mode 100644 index 0000000..3377610 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/help/make.bat @@ -0,0 +1,155 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\template_class.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\template_class.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/QGIS_plugin/ecmwf_basins/help/source/conf.py b/QGIS_plugin/ecmwf_basins/help/source/conf.py new file mode 100644 index 0000000..cce3724 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/help/source/conf.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +# +# Update basins documentation build configuration file, created by +# sphinx-quickstart on Sun Feb 12 17:11:03 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.todo', 'sphinx.ext.imgmath', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'ECMWF Basins' +copyright = u'2013, mundialis' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_TemplateModuleNames = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'TemplateClassdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'basins.tex', u'basins Documentation', + u'mundialis', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'TemplateClass', u'basins Documentation', + [u'mundialis'], 1) +] diff --git a/QGIS_plugin/ecmwf_basins/help/source/index.rst b/QGIS_plugin/ecmwf_basins/help/source/index.rst new file mode 100644 index 0000000..5aa11f5 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/help/source/index.rst @@ -0,0 +1,20 @@ +.. basins documentation master file, created by + sphinx-quickstart on Sun Feb 12 17:11:03 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to basins's documentation! +============================================ + +Contents: + +.. toctree:: + :maxdepth: 2 + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/QGIS_plugin/ecmwf_basins/i18n/af.ts b/QGIS_plugin/ecmwf_basins/i18n/af.ts new file mode 100644 index 0000000..615a88c --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/i18n/af.ts @@ -0,0 +1,11 @@ + + + + @default + + + Good morning + Goeie more + + + diff --git a/QGIS_plugin/ecmwf_basins/icon.png b/QGIS_plugin/ecmwf_basins/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f696c00303219cfcc2f0d57608d04e298fbf56c3 GIT binary patch literal 1034 zcmV+l1oiugP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXD@ z77;9+NQ1io00W9iL_t(Y$E}r1Xk1ko$A9PCduQg(OlBrY(9Ks7iMEcmzAKc3;VzZCAM*45PN_;^e6>czpi>a)k;jWgn$XwX#(ffW0x= z+8`f&KFz}YS*DXYlF=gSn-KxzBG6N#o}Sbu5ylW$ifX;y!Gxcoccb7X?l*&Cg%BLp+upi(AsPbFBN>WXDC?; z&(>7p1`~%XZ)m0`uQ4(<$FhGH?|poXGiT2da3o4;)@nx9({OUBf@d^Etti_X6*QRt zM(yu64Vk}6JTN%G$V;!1TAbqGBW-lFx`bT|DMfv_l!BJF5c985o@kjA(}*tNFK{QCU>`#$u@1vR=}>qa5@&kCuGhvP`xkhN}@YN}=O zQj$Y2Uc!t58W8mdtiyG1gOY%=`Oqf#kFzPpKDdk-HFaxyemu%W41P&5^jJIh_B4=) zIRqS8AO73bJYStlGW|rdy4U))V9(wb+>k{~N-CaW{!Ycn!A{zChUn=E(`kQe>l33Is7?7;qutHE1STpm8YoYyRmhySVLwneD zILtlW(W=jJRx_{aH|L?0Ar`gK1}rHy^>Uk*mB7T>op^&oQzC$62^{-|!2sta za1=;y9wJfWb=kJ1ful09`}7PjxUqtpunAx=vHSGQzo6-Eu3FzqfdBvi07*qoM6N<$ Ef=x2zx&QzG literal 0 HcmV?d00001 diff --git a/QGIS_plugin/ecmwf_basins/metadata.txt b/QGIS_plugin/ecmwf_basins/metadata.txt new file mode 100644 index 0000000..564b763 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/metadata.txt @@ -0,0 +1,47 @@ +# This file contains metadata for your plugin. + +# This file should be included when you package your plugin.# Mandatory items: + +[general] +name=ECMWF basins +qgisMinimumVersion=3.0 +description=This plugin updates ECMWF basins +version=0.1 +author=mundialis +email=metz@mundialis.de + +about=Update ECMWF basins. + +tracker=http://bugs +repository=http://repo +# End of mandatory metadata + +# Recommended items: + +hasProcessingProvider=no +# Uncomment the following line and add your changelog: +# changelog= + +# Tags are comma separated with spaces allowed +tags=python + +homepage=http://homepage +category=Plugins +icon=icon.png +# experimental flag +experimental=True + +# deprecated flag (applies to the whole plugin, not just a single version) +deprecated=False + +# Since QGIS 3.8, a comma separated list of plugins to be installed +# (or upgraded) can be specified. +# Check the documentation for more information. +# plugin_dependencies= + +Category of the plugin: Raster, Vector, Database or Web +# category= + +# If the plugin can run on QGIS Server. +server=False + diff --git a/QGIS_plugin/ecmwf_basins/pb_tool.cfg b/QGIS_plugin/ecmwf_basins/pb_tool.cfg new file mode 100644 index 0000000..0feb2be --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/pb_tool.cfg @@ -0,0 +1,80 @@ +#/*************************************************************************** +# Basins +# +# Configuration file for plugin builder tool (pb_tool) +# Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ +# ------------------- +# begin : 2019-08-26 +# copyright : (C) 2019 by mundialis +# email : metz@mundialis.de +# ***************************************************************************/ +# +#/*************************************************************************** +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU General Public License as published by * +# * the Free Software Foundation; either version 2 of the License, or * +# * (at your option) any later version. * +# * * +# ***************************************************************************/ +# +# +# You can install pb_tool using: +# pip install http://geoapt.net/files/pb_tool.zip +# +# Consider doing your development (and install of pb_tool) in a virtualenv. +# +# For details on setting up and using pb_tool, see: +# http://g-sherman.github.io/plugin_build_tool/ +# +# Issues and pull requests here: +# https://github.com/g-sherman/plugin_build_tool: +# +# Sane defaults for your plugin generated by the Plugin Builder are +# already set below. +# +# As you add Python source files and UI files to your plugin, add +# them to the appropriate [files] section below. + +[plugin] +# Name of the plugin. This is the name of the directory that will +# be created in .qgis2/python/plugins +name: ecmwf_basins + +# Full path to where you want your plugin directory copied. If empty, +# the QGIS default path will be used. Don't include the plugin name in +# the path. +plugin_path: + +[files] +# Python files that should be deployed with the plugin +python_files: __init__.py ecmwf_basins.py ecmwf_basins_dialog.py + +# The main dialog file that is loaded (not compiled) +main_dialog: ecmwf_basins_dialog_base.ui + +# Other ui files for dialogs you create (these will be compiled) +compiled_ui_files: + +# Resource file(s) that will be compiled +resource_files: resources.qrc + +# Other files required for the plugin +extras: metadata.txt icon.png + +# Other directories to be deployed with the plugin. +# These must be subdirectories under the plugin directory +extra_dirs: + +# ISO code(s) for any locales (translations), separated by spaces. +# Corresponding .ts files must exist in the i18n directory +locales: + +[help] +# the built help directory that should be deployed with the plugin +dir: help/build/html +# the name of the directory to target in the deployed plugin +target: help + + + diff --git a/QGIS_plugin/ecmwf_basins/plugin_upload.py b/QGIS_plugin/ecmwf_basins/plugin_upload.py new file mode 100755 index 0000000..a88ea2b --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/plugin_upload.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# coding=utf-8 +"""This script uploads a plugin package to the plugin repository. + Authors: A. Pasotti, V. Picavet + git sha : $TemplateVCSFormat +""" + +import sys +import getpass +import xmlrpc.client +from optparse import OptionParser + +standard_library.install_aliases() + +# Configuration +PROTOCOL = 'https' +SERVER = 'plugins.qgis.org' +PORT = '443' +ENDPOINT = '/plugins/RPC2/' +VERBOSE = False + + +def main(parameters, arguments): + """Main entry point. + + :param parameters: Command line parameters. + :param arguments: Command line arguments. + """ + address = "{protocol}://{username}:{password}@{server}:{port}{endpoint}".format( + protocol=PROTOCOL, + username=parameters.username, + password=parameters.password, + server=parameters.server, + port=parameters.port, + endpoint=ENDPOINT) + print("Connecting to: %s" % hide_password(address)) + + server = xmlrpc.client.ServerProxy(address, verbose=VERBOSE) + + try: + with open(arguments[0], 'rb') as handle: + plugin_id, version_id = server.plugin.upload( + xmlrpc.client.Binary(handle.read())) + print("Plugin ID: %s" % plugin_id) + print("Version ID: %s" % version_id) + except xmlrpc.client.ProtocolError as err: + print("A protocol error occurred") + print("URL: %s" % hide_password(err.url, 0)) + print("HTTP/HTTPS headers: %s" % err.headers) + print("Error code: %d" % err.errcode) + print("Error message: %s" % err.errmsg) + except xmlrpc.client.Fault as err: + print("A fault occurred") + print("Fault code: %d" % err.faultCode) + print("Fault string: %s" % err.faultString) + + +def hide_password(url, start=6): + """Returns the http url with password part replaced with '*'. + + :param url: URL to upload the plugin to. + :type url: str + + :param start: Position of start of password. + :type start: int + """ + start_position = url.find(':', start) + 1 + end_position = url.find('@') + return "%s%s%s" % ( + url[:start_position], + '*' * (end_position - start_position), + url[end_position:]) + + +if __name__ == "__main__": + parser = OptionParser(usage="%prog [options] plugin.zip") + parser.add_option( + "-w", "--password", dest="password", + help="Password for plugin site", metavar="******") + parser.add_option( + "-u", "--username", dest="username", + help="Username of plugin site", metavar="user") + parser.add_option( + "-p", "--port", dest="port", + help="Server port to connect to", metavar="80") + parser.add_option( + "-s", "--server", dest="server", + help="Specify server name", metavar="plugins.qgis.org") + options, args = parser.parse_args() + if len(args) != 1: + print("Please specify zip file.\n") + parser.print_help() + sys.exit(1) + if not options.server: + options.server = SERVER + if not options.port: + options.port = PORT + if not options.username: + # interactive mode + username = getpass.getuser() + print("Please enter user name [%s] :" % username, end=' ') + + res = input() + if res != "": + options.username = res + else: + options.username = username + if not options.password: + # interactive mode + options.password = getpass.getpass() + main(options, args) diff --git a/QGIS_plugin/ecmwf_basins/pylintrc b/QGIS_plugin/ecmwf_basins/pylintrc new file mode 100644 index 0000000..7e168f6 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/pylintrc @@ -0,0 +1,281 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# see http://stackoverflow.com/questions/21487025/pylint-locally-defined-disables-still-give-warnings-how-to-suppress-them +disable=locally-disabled,C0103 + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct attribute names in class +# bodies +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/QGIS_plugin/ecmwf_basins/resources.py b/QGIS_plugin/ecmwf_basins/resources.py new file mode 100644 index 0000000..5528658 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/resources.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created by: The Resource Compiler for PyQt5 (Qt v5.12.4) +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore + +qt_resource_data = b"\ +\x00\x00\x04\x0a\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x17\x00\x00\x00\x18\x08\x06\x00\x00\x00\x11\x7c\x66\x75\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd9\x02\x15\ +\x16\x11\x2c\x9d\x48\x83\xbb\x00\x00\x03\x8a\x49\x44\x41\x54\x48\ +\xc7\xad\x95\x4b\x68\x5c\x55\x18\xc7\x7f\xe7\xdc\x7b\x67\xe6\xce\ +\x4c\x66\x26\x49\xd3\x24\x26\xa6\xc6\xf8\x40\x21\xa5\x04\xb3\x28\ +\xda\x98\x20\xa5\x0b\xad\x55\xa8\x2b\xc5\x50\x1f\xa0\x6e\x34\x2b\ +\x45\x30\x14\x02\xba\x52\x69\x15\x17\x66\x63\x45\x97\x95\xa0\xad\ +\x0b\xfb\xc0\x06\x25\xb6\x71\x61\x12\x41\x50\xdb\x2a\x21\xd1\xe2\ +\x24\xf3\x9e\xc9\xcc\xbd\xe7\x1c\x17\x35\x43\x1e\x33\x21\xb6\xfd\ +\x56\x87\xf3\x9d\xfb\xfb\x1e\xf7\xff\x9d\x23\x8c\x31\x43\x95\xf4\ +\x85\x1e\x3f\x3b\x35\xac\xfd\xcc\x43\xdc\xa4\x49\x3b\xfe\x9d\x1d\ +\xdb\x7b\x22\x90\x78\xf8\xb2\x28\xa7\xbe\x7d\xc1\x4b\x9d\x79\xdf\ +\x18\x15\xe5\x16\x99\x10\x56\xde\x69\xdc\x3f\x22\xfd\xec\xd4\xf0\ +\xad\x04\x03\x18\xa3\xa2\x7e\x76\x6a\x58\xde\x68\x2b\xb4\x36\xf8\ +\xbe\xc6\x18\x53\xdb\xef\xe7\xfa\xec\xed\x67\x63\x10\x42\x00\xf0\ +\xfb\xd5\x65\x2a\x15\x45\xc7\x6d\x0d\x00\xc4\xa2\xc1\xaa\x6f\x0d\ +\x3e\x6c\xab\xc2\x1c\x56\xa4\x77\x4b\xb0\xf2\x35\x15\x5f\x21\x85\ +\xe0\xc8\x6b\x5f\x92\x2d\x37\x33\x39\xf9\x03\x27\x8e\x1f\xa2\xf7\ +\xbe\x9d\x04\x1c\x0b\x37\xe4\xac\xff\xa6\x30\x87\xbd\xba\x00\x6a\ +\x06\x79\xe5\xf5\xaf\x89\xd9\x92\xc5\xcc\x0a\xd9\x7c\x19\xcf\xe9\ +\xe2\xe4\xa9\x2f\x78\x7c\xff\x01\x72\x85\x0a\x2b\x65\x1f\xa5\x4c\ +\xb5\xb2\x55\x16\x80\xbd\x31\xda\xda\x20\x1f\x7d\x3e\xcd\xc2\xfd\ +\x59\xa6\x93\x39\x92\xd1\x22\xea\x9b\x16\xce\x9d\x3f\xce\xe0\x83\ +\x03\x24\x82\x59\x3a\xdb\x7b\x88\xc7\x82\x68\x63\x58\xc9\xcc\x62\ +\x8c\x21\x18\xb0\x6a\xc3\x37\x06\x49\x16\xff\x24\x6b\xa5\x49\xbb\ +\x25\xbc\xa2\xa6\x21\xbb\x40\x7f\xdf\x00\x83\xbd\x01\x8e\x3c\xd5\ +\x45\xd7\x8e\x6b\x9c\x9c\x98\x25\x1a\xb6\xe8\xbe\x3d\xc2\xdd\x77\ +\x44\x48\xc4\x1c\x22\xe1\xeb\x58\x59\xaf\xcf\xd3\x33\x29\x2e\x34\ +\x2d\x91\x93\x3e\xbe\x34\x78\x01\xc5\xe2\x61\xc5\xae\x72\x8e\x70\ +\xc8\xc2\x0d\x5a\xbc\xf5\xee\x2f\x9c\xfa\x3e\x86\x69\x7a\x8e\xcf\ +\x26\xe6\xf9\x63\xa1\x44\xa1\xa4\xd0\xda\x6c\x0d\x2f\x15\x7c\xb4\ +\x67\x28\x59\x0a\xcf\xd6\x54\xe2\x06\x13\x87\x2b\x6f\x68\xa6\x27\ +\xaf\x31\x32\x36\xc7\xb2\x7f\x17\xef\x7d\x7c\x8c\x33\x67\xcf\x12\ +\x70\x24\x4a\x69\xd6\x6a\x46\xd6\xd3\x70\x72\xa9\x82\x67\x34\x45\ +\xad\x28\xdb\x1a\x15\x34\x98\xff\x46\xed\xef\x37\x0d\x99\xbf\x4a\ +\x3c\x30\x38\xc0\xc8\x4b\xaf\x92\x5a\x9c\xe2\xe0\x23\x6d\x74\xb4\ +\xba\x84\x5d\x0b\x29\x45\x7d\xb8\x94\x82\x96\xb6\x10\xf3\xc5\x12\ +\x2a\xef\x53\x11\x1a\x63\xad\x3f\x93\x19\x85\xf1\xb1\x77\x58\x5a\ +\xf8\x99\x97\x9f\xe9\xa6\x75\x47\x90\xc6\xb8\x43\xd8\xb5\xb6\xce\ +\xfc\xfa\xfd\x00\xfb\x3e\xf4\xc8\x05\x35\xba\x5e\xeb\x46\x21\xf9\ +\xcf\x0a\xa9\x8c\x87\xe3\x48\xdc\x90\xb5\x6e\x98\x6a\xaa\x65\xf2\ +\x52\x92\x43\x2f\x5e\xc2\x8c\x02\x1a\x10\xf5\x07\xac\xc3\x75\x70\ +\x83\x92\x80\xb3\xf9\xd0\x26\xf8\x8f\xb3\x29\xc6\x3e\xb8\x8c\x19\ +\x35\x75\x6b\x7b\x7e\x3c\xca\x45\x0c\x7e\x49\x31\xf4\x58\x3b\xf7\ +\xf6\x34\x90\x88\x39\x04\x1c\x59\x1f\xfe\xdb\xd5\x3c\x5f\x9d\x4b\ +\x32\xfd\x44\xb2\xba\xd7\xfa\xb6\x60\xcf\xde\x16\xdc\x90\x45\x4c\ +\x4a\x2a\x9e\x62\xfe\x4e\xc5\xc8\xc1\x4e\xda\x76\x86\xe8\xe9\x0a\ +\xe3\xd8\x92\x58\xd4\xc6\xb2\x44\x6d\x78\x2a\x53\xe1\xca\x7c\x99\ +\x63\x5d\xbf\x56\x9d\xbd\x9f\x44\x18\x7a\xba\x95\x27\x0f\xb4\xd3\ +\xdc\x18\xc0\xf3\x0d\x52\x40\xd8\xb5\xb0\xa4\x20\x14\xb2\x70\x6c\ +\x81\x63\xcb\xaa\x42\xd6\xfd\xb7\xf4\xec\xa3\x06\xa0\x50\x52\xd8\ +\x4e\x1b\x7e\x4a\xd3\x31\xf9\x29\xcf\xfe\xd4\x49\x7f\x5f\x13\xfb\ +\xfa\x9b\x71\x43\x92\x58\xd4\x21\x18\x90\xac\xde\xb0\x42\x50\x13\ +\x58\x33\xf3\x88\x6b\xa1\xfd\x65\x96\xf2\x79\xc6\x43\x7b\xd8\x75\ +\x38\xcc\x3d\xdd\xd1\xaa\xcf\x71\xe4\xff\x7f\x91\x56\x33\xaf\xea\ +\x37\xe7\xa1\x94\x21\x16\xb5\xd1\x06\x2c\x29\x36\xf5\x72\x9b\x96\ +\x95\xc0\xc4\xda\x9d\x78\x83\x43\x53\x22\x80\x65\x09\x1c\xfb\x86\ +\xc1\x00\xe7\x25\x70\x14\x48\x6f\x1e\x22\x51\xe3\x75\xd9\xb6\xa5\ +\x81\xa3\x32\xb1\xfb\xf4\x0c\x30\xb8\xb1\x82\x9b\xb0\x09\x60\x30\ +\xb1\xfb\xf4\xcc\xbf\xa0\xe9\x6e\xae\x5a\xdf\x4b\x81\x00\x00\x00\ +\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +" + +qt_resource_name = b"\ +\x00\x07\ +\x07\x3b\xe0\xb3\ +\x00\x70\ +\x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\ +\x00\x10\ +\x00\xe4\xfd\x73\ +\x00\x65\ +\x00\x63\x00\x6d\x00\x77\x00\x66\x00\x5f\x00\x67\x00\x72\x00\x69\x00\x64\x00\x70\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\ +\x00\x08\ +\x0a\x61\x5a\xa7\ +\x00\x69\ +\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +" + +qt_resource_struct_v1 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ +\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +" + +qt_resource_struct_v2 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x6c\xd3\xbe\x16\x94\ +" + +qt_version = [int(v) for v in QtCore.qVersion().split('.')] +if qt_version < [5, 8, 0]: + rcc_version = 1 + qt_resource_struct = qt_resource_struct_v1 +else: + rcc_version = 2 + qt_resource_struct = qt_resource_struct_v2 + +def qInitResources(): + QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/QGIS_plugin/ecmwf_basins/resources.qrc b/QGIS_plugin/ecmwf_basins/resources.qrc new file mode 100644 index 0000000..18aa86a --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/resources.qrc @@ -0,0 +1,5 @@ + + + icon.png + + diff --git a/QGIS_plugin/ecmwf_basins/scripts/compile-strings.sh b/QGIS_plugin/ecmwf_basins/scripts/compile-strings.sh new file mode 100644 index 0000000..9d76083 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/scripts/compile-strings.sh @@ -0,0 +1,12 @@ +#!/bin/bash +LRELEASE=$1 +LOCALES=$2 + + +for LOCALE in ${LOCALES} +do + echo "Processing: ${LOCALE}.ts" + # Note we don't use pylupdate with qt .pro file approach as it is flakey + # about what is made available. + $LRELEASE i18n/${LOCALE}.ts +done diff --git a/QGIS_plugin/ecmwf_basins/scripts/run-env-linux.sh b/QGIS_plugin/ecmwf_basins/scripts/run-env-linux.sh new file mode 100644 index 0000000..668247c --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/scripts/run-env-linux.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +QGIS_PREFIX_PATH=/usr/local/qgis-2.0 +if [ -n "$1" ]; then + QGIS_PREFIX_PATH=$1 +fi + +echo ${QGIS_PREFIX_PATH} + + +export QGIS_PREFIX_PATH=${QGIS_PREFIX_PATH} +export QGIS_PATH=${QGIS_PREFIX_PATH} +export LD_LIBRARY_PATH=${QGIS_PREFIX_PATH}/lib +export PYTHONPATH=${QGIS_PREFIX_PATH}/share/qgis/python:${QGIS_PREFIX_PATH}/share/qgis/python/plugins:${PYTHONPATH} + +echo "QGIS PATH: $QGIS_PREFIX_PATH" +export QGIS_DEBUG=0 +export QGIS_LOG_FILE=/tmp/inasafe/realtime/logs/qgis.log + +export PATH=${QGIS_PREFIX_PATH}/bin:$PATH + +echo "This script is intended to be sourced to set up your shell to" +echo "use a QGIS 2.0 built in $QGIS_PREFIX_PATH" +echo +echo "To use it do:" +echo "source $BASH_SOURCE /your/optional/install/path" +echo +echo "Then use the make file supplied here e.g. make guitest" diff --git a/QGIS_plugin/ecmwf_basins/scripts/update-strings.sh b/QGIS_plugin/ecmwf_basins/scripts/update-strings.sh new file mode 100644 index 0000000..a31f712 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/scripts/update-strings.sh @@ -0,0 +1,56 @@ +#!/bin/bash +LOCALES=$* + +# Get newest .py files so we don't update strings unnecessarily + +CHANGED_FILES=0 +PYTHON_FILES=`find . -regex ".*\(ui\|py\)$" -type f` +for PYTHON_FILE in $PYTHON_FILES +do + CHANGED=$(stat -c %Y $PYTHON_FILE) + if [ ${CHANGED} -gt ${CHANGED_FILES} ] + then + CHANGED_FILES=${CHANGED} + fi +done + +# Qt translation stuff +# for .ts file +UPDATE=false +for LOCALE in ${LOCALES} +do + TRANSLATION_FILE="i18n/$LOCALE.ts" + if [ ! -f ${TRANSLATION_FILE} ] + then + # Force translation string collection as we have a new language file + touch ${TRANSLATION_FILE} + UPDATE=true + break + fi + + MODIFICATION_TIME=$(stat -c %Y ${TRANSLATION_FILE}) + if [ ${CHANGED_FILES} -gt ${MODIFICATION_TIME} ] + then + # Force translation string collection as a .py file has been updated + UPDATE=true + break + fi +done + +if [ ${UPDATE} == true ] +# retrieve all python files +then + echo ${PYTHON_FILES} + # update .ts + echo "Please provide translations by editing the translation files below:" + for LOCALE in ${LOCALES} + do + echo "i18n/"${LOCALE}".ts" + # Note we don't use pylupdate with qt .pro file approach as it is flakey + # about what is made available. + pylupdate4 -noobsolete ${PYTHON_FILES} -ts i18n/${LOCALE}.ts + done +else + echo "No need to edit any translation files (.ts) because no python files" + echo "has been updated since the last update translation. " +fi diff --git a/QGIS_plugin/ecmwf_basins/test/__init__.py b/QGIS_plugin/ecmwf_basins/test/__init__.py new file mode 100644 index 0000000..8feeb0b --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/__init__.py @@ -0,0 +1,2 @@ +# import qgis libs so that ve set the correct sip api version +import qgis # pylint: disable=W0611 # NOQA \ No newline at end of file diff --git a/QGIS_plugin/ecmwf_basins/test/qgis_interface.py b/QGIS_plugin/ecmwf_basins/test/qgis_interface.py new file mode 100644 index 0000000..a407052 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/qgis_interface.py @@ -0,0 +1,205 @@ +# coding=utf-8 +"""QGIS plugin implementation. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +.. note:: This source code was copied from the 'postgis viewer' application + with original authors: + Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk + Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org + Copyright (c) 2014 Tim Sutton, tim@linfiniti.com + +""" + +__author__ = 'tim@linfiniti.com' +__revision__ = '$Format:%H$' +__date__ = '10/01/2011' +__copyright__ = ( + 'Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and ' + 'Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org' + 'Copyright (c) 2014 Tim Sutton, tim@linfiniti.com' +) + +import logging +from qgis.PyQt.QtCore import QObject, pyqtSlot, pyqtSignal +from qgis.core import QgsMapLayerRegistry +from qgis.gui import QgsMapCanvasLayer +LOGGER = logging.getLogger('QGIS') + + +#noinspection PyMethodMayBeStatic,PyPep8Naming +class QgisInterface(QObject): + """Class to expose QGIS objects and functions to plugins. + + This class is here for enabling us to run unit tests only, + so most methods are simply stubs. + """ + currentLayerChanged = pyqtSignal(QgsMapCanvasLayer) + + def __init__(self, canvas): + """Constructor + :param canvas: + """ + QObject.__init__(self) + self.canvas = canvas + # Set up slots so we can mimic the behaviour of QGIS when layers + # are added. + LOGGER.debug('Initialising canvas...') + # noinspection PyArgumentList + QgsMapLayerRegistry.instance().layersAdded.connect(self.addLayers) + # noinspection PyArgumentList + QgsMapLayerRegistry.instance().layerWasAdded.connect(self.addLayer) + # noinspection PyArgumentList + QgsMapLayerRegistry.instance().removeAll.connect(self.removeAllLayers) + + # For processing module + self.destCrs = None + + @pyqtSlot('QStringList') + def addLayers(self, layers): + """Handle layers being added to the registry so they show up in canvas. + + :param layers: list list of map layers that were added + + .. note:: The QgsInterface api does not include this method, + it is added here as a helper to facilitate testing. + """ + #LOGGER.debug('addLayers called on qgis_interface') + #LOGGER.debug('Number of layers being added: %s' % len(layers)) + #LOGGER.debug('Layer Count Before: %s' % len(self.canvas.layers())) + current_layers = self.canvas.layers() + final_layers = [] + for layer in current_layers: + final_layers.append(QgsMapCanvasLayer(layer)) + for layer in layers: + final_layers.append(QgsMapCanvasLayer(layer)) + + self.canvas.setLayerSet(final_layers) + #LOGGER.debug('Layer Count After: %s' % len(self.canvas.layers())) + + @pyqtSlot('QgsMapLayer') + def addLayer(self, layer): + """Handle a layer being added to the registry so it shows up in canvas. + + :param layer: list list of map layers that were added + + .. note: The QgsInterface api does not include this method, it is added + here as a helper to facilitate testing. + + .. note: The addLayer method was deprecated in QGIS 1.8 so you should + not need this method much. + """ + pass + + @pyqtSlot() + def removeAllLayers(self): + """Remove layers from the canvas before they get deleted.""" + self.canvas.setLayerSet([]) + + def newProject(self): + """Create new project.""" + # noinspection PyArgumentList + QgsMapLayerRegistry.instance().removeAllMapLayers() + + # ---------------- API Mock for QgsInterface follows ------------------- + + def zoomFull(self): + """Zoom to the map full extent.""" + pass + + def zoomToPrevious(self): + """Zoom to previous view extent.""" + pass + + def zoomToNext(self): + """Zoom to next view extent.""" + pass + + def zoomToActiveLayer(self): + """Zoom to extent of active layer.""" + pass + + def addVectorLayer(self, path, base_name, provider_key): + """Add a vector layer. + + :param path: Path to layer. + :type path: str + + :param base_name: Base name for layer. + :type base_name: str + + :param provider_key: Provider key e.g. 'ogr' + :type provider_key: str + """ + pass + + def addRasterLayer(self, path, base_name): + """Add a raster layer given a raster layer file name + + :param path: Path to layer. + :type path: str + + :param base_name: Base name for layer. + :type base_name: str + """ + pass + + def activeLayer(self): + """Get pointer to the active layer (layer selected in the legend).""" + # noinspection PyArgumentList + layers = QgsMapLayerRegistry.instance().mapLayers() + for item in layers: + return layers[item] + + def addToolBarIcon(self, action): + """Add an icon to the plugins toolbar. + + :param action: Action to add to the toolbar. + :type action: QAction + """ + pass + + def removeToolBarIcon(self, action): + """Remove an action (icon) from the plugin toolbar. + + :param action: Action to add to the toolbar. + :type action: QAction + """ + pass + + def addToolBar(self, name): + """Add toolbar with specified name. + + :param name: Name for the toolbar. + :type name: str + """ + pass + + def mapCanvas(self): + """Return a pointer to the map canvas.""" + return self.canvas + + def mainWindow(self): + """Return a pointer to the main window. + + In case of QGIS it returns an instance of QgisApp. + """ + pass + + def addDockWidget(self, area, dock_widget): + """Add a dock widget to the main window. + + :param area: Where in the ui the dock should be placed. + :type area: + + :param dock_widget: A dock widget to add to the UI. + :type dock_widget: QDockWidget + """ + pass + + def legendInterface(self): + """Get the legend.""" + return self.canvas diff --git a/QGIS_plugin/ecmwf_basins/test/tenbytenraster.asc b/QGIS_plugin/ecmwf_basins/test/tenbytenraster.asc new file mode 100644 index 0000000..96a0ee1 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/tenbytenraster.asc @@ -0,0 +1,19 @@ +NCOLS 10 +NROWS 10 +XLLCENTER 1535380.000000 +YLLCENTER 5083260.000000 +DX 10 +DY 10 +NODATA_VALUE -9999 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 +CRS +NOTES diff --git a/QGIS_plugin/ecmwf_basins/test/tenbytenraster.asc.aux.xml b/QGIS_plugin/ecmwf_basins/test/tenbytenraster.asc.aux.xml new file mode 100644 index 0000000..cfb1578 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/tenbytenraster.asc.aux.xml @@ -0,0 +1,13 @@ + + + Point + + + + 9 + 4.5 + 0 + 2.872281323269 + + + diff --git a/QGIS_plugin/ecmwf_basins/test/tenbytenraster.keywords b/QGIS_plugin/ecmwf_basins/test/tenbytenraster.keywords new file mode 100644 index 0000000..8be3f61 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/tenbytenraster.keywords @@ -0,0 +1 @@ +title: Tenbytenraster diff --git a/QGIS_plugin/ecmwf_basins/test/tenbytenraster.lic b/QGIS_plugin/ecmwf_basins/test/tenbytenraster.lic new file mode 100644 index 0000000..8345533 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/tenbytenraster.lic @@ -0,0 +1,18 @@ + + + + Tim Sutton, Linfiniti Consulting CC + + + + tenbytenraster.asc + 2700044251 + Yes + Tim Sutton + Tim Sutton (QGIS Source Tree) + Tim Sutton + This data is publicly available from QGIS Source Tree. The original + file was created and contributed to QGIS by Tim Sutton. + + + diff --git a/QGIS_plugin/ecmwf_basins/test/tenbytenraster.prj b/QGIS_plugin/ecmwf_basins/test/tenbytenraster.prj new file mode 100644 index 0000000..a30c00a --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/tenbytenraster.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/QGIS_plugin/ecmwf_basins/test/tenbytenraster.qml b/QGIS_plugin/ecmwf_basins/test/tenbytenraster.qml new file mode 100644 index 0000000..85247d4 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/tenbytenraster.qml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/QGIS_plugin/ecmwf_basins/test/test_ecmwf_basins_dialog.py b/QGIS_plugin/ecmwf_basins/test/test_ecmwf_basins_dialog.py new file mode 100644 index 0000000..f879360 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/test_ecmwf_basins_dialog.py @@ -0,0 +1,55 @@ +# coding=utf-8 +"""Dialog test. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +""" + +__author__ = 'metz@mundialis.de' +__date__ = '2019-08-26' +__copyright__ = 'Copyright 2019, mundialis' + +import unittest + +from qgis.PyQt.QtGui import QDialogButtonBox, QDialog + +from ecmwf_basins_dialog import BasinsDialog + +from utilities import get_qgis_app +QGIS_APP = get_qgis_app() + + +class BasinsDialogTest(unittest.TestCase): + """Test dialog works.""" + + def setUp(self): + """Runs before each test.""" + self.dialog = BasinsDialog(None) + + def tearDown(self): + """Runs after each test.""" + self.dialog = None + + def test_dialog_ok(self): + """Test we can click OK.""" + + button = self.dialog.button_box.button(QDialogButtonBox.Ok) + button.click() + result = self.dialog.result() + self.assertEqual(result, QDialog.Accepted) + + def test_dialog_cancel(self): + """Test we can click cancel.""" + button = self.dialog.button_box.button(QDialogButtonBox.Cancel) + button.click() + result = self.dialog.result() + self.assertEqual(result, QDialog.Rejected) + +if __name__ == "__main__": + suite = unittest.makeSuite(BasinsDialogTest) + runner = unittest.TextTestRunner(verbosity=2) + runner.run(suite) + diff --git a/QGIS_plugin/ecmwf_basins/test/test_init.py b/QGIS_plugin/ecmwf_basins/test/test_init.py new file mode 100644 index 0000000..a11ca44 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/test_init.py @@ -0,0 +1,64 @@ +# coding=utf-8 +"""Tests QGIS plugin init.""" + +__author__ = 'Tim Sutton ' +__revision__ = '$Format:%H$' +__date__ = '17/10/2010' +__license__ = "GPL" +__copyright__ = 'Copyright 2012, Australia Indonesia Facility for ' +__copyright__ += 'Disaster Reduction' + +import os +import unittest +import logging +import configparser + +LOGGER = logging.getLogger('QGIS') + + +class TestInit(unittest.TestCase): + """Test that the plugin init is usable for QGIS. + + Based heavily on the validator class by Alessandro + Passoti available here: + + http://github.com/qgis/qgis-django/blob/master/qgis-app/ + plugins/validator.py + + """ + + def test_read_init(self): + """Test that the plugin __init__ will validate on plugins.qgis.org.""" + + # You should update this list according to the latest in + # https://github.com/qgis/qgis-django/blob/master/qgis-app/ + # plugins/validator.py + + required_metadata = [ + 'name', + 'description', + 'version', + 'qgisMinimumVersion', + 'email', + 'author'] + + file_path = os.path.abspath(os.path.join( + os.path.dirname(__file__), os.pardir, + 'metadata.txt')) + LOGGER.info(file_path) + metadata = [] + parser = configparser.ConfigParser() + parser.optionxform = str + parser.read(file_path) + message = 'Cannot find a section named "general" in %s' % file_path + assert parser.has_section('general'), message + metadata.extend(parser.items('general')) + + for expectation in required_metadata: + message = ('Cannot find metadata "%s" in metadata source (%s).' % ( + expectation, file_path)) + + self.assertIn(expectation, dict(metadata), message) + +if __name__ == '__main__': + unittest.main() diff --git a/QGIS_plugin/ecmwf_basins/test/test_qgis_environment.py b/QGIS_plugin/ecmwf_basins/test/test_qgis_environment.py new file mode 100644 index 0000000..1becb30 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/test_qgis_environment.py @@ -0,0 +1,60 @@ +# coding=utf-8 +"""Tests for QGIS functionality. + + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +""" +__author__ = 'tim@linfiniti.com' +__date__ = '20/01/2011' +__copyright__ = ('Copyright 2012, Australia Indonesia Facility for ' + 'Disaster Reduction') + +import os +import unittest +from qgis.core import ( + QgsProviderRegistry, + QgsCoordinateReferenceSystem, + QgsRasterLayer) + +from .utilities import get_qgis_app +QGIS_APP = get_qgis_app() + + +class QGISTest(unittest.TestCase): + """Test the QGIS Environment""" + + def test_qgis_environment(self): + """QGIS environment has the expected providers""" + + r = QgsProviderRegistry.instance() + self.assertIn('gdal', r.providerList()) + self.assertIn('ogr', r.providerList()) + self.assertIn('postgres', r.providerList()) + + def test_projection(self): + """Test that QGIS properly parses a wkt string. + """ + crs = QgsCoordinateReferenceSystem() + wkt = ( + 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",' + 'SPHEROID["WGS_1984",6378137.0,298.257223563]],' + 'PRIMEM["Greenwich",0.0],UNIT["Degree",' + '0.0174532925199433]]') + crs.createFromWkt(wkt) + auth_id = crs.authid() + expected_auth_id = 'EPSG:4326' + self.assertEqual(auth_id, expected_auth_id) + + # now test for a loaded layer + path = os.path.join(os.path.dirname(__file__), 'tenbytenraster.asc') + title = 'TestRaster' + layer = QgsRasterLayer(path, title) + auth_id = layer.crs().authid() + self.assertEqual(auth_id, expected_auth_id) + +if __name__ == '__main__': + unittest.main() diff --git a/QGIS_plugin/ecmwf_basins/test/test_resources.py b/QGIS_plugin/ecmwf_basins/test/test_resources.py new file mode 100644 index 0000000..2969d06 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/test_resources.py @@ -0,0 +1,44 @@ +# coding=utf-8 +"""Resources test. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +""" + +__author__ = 'metz@mundialis.de' +__date__ = '2019-08-26' +__copyright__ = 'Copyright 2019, mundialis' + +import unittest + +from qgis.PyQt.QtGui import QIcon + + + +class BasinsDialogTest(unittest.TestCase): + """Test rerources work.""" + + def setUp(self): + """Runs before each test.""" + pass + + def tearDown(self): + """Runs after each test.""" + pass + + def test_icon_png(self): + """Test we can click OK.""" + path = ':/plugins/Basins/icon.png' + icon = QIcon(path) + self.assertFalse(icon.isNull()) + +if __name__ == "__main__": + suite = unittest.makeSuite(BasinsResourcesTest) + runner = unittest.TextTestRunner(verbosity=2) + runner.run(suite) + + + diff --git a/QGIS_plugin/ecmwf_basins/test/test_translations.py b/QGIS_plugin/ecmwf_basins/test/test_translations.py new file mode 100644 index 0000000..035dc62 --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/test_translations.py @@ -0,0 +1,55 @@ +# coding=utf-8 +"""Safe Translations Test. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +""" +from .utilities import get_qgis_app + +__author__ = 'ismailsunni@yahoo.co.id' +__date__ = '12/10/2011' +__copyright__ = ('Copyright 2012, Australia Indonesia Facility for ' + 'Disaster Reduction') +import unittest +import os + +from qgis.PyQt.QtCore import QCoreApplication, QTranslator + +QGIS_APP = get_qgis_app() + + +class SafeTranslationsTest(unittest.TestCase): + """Test translations work.""" + + def setUp(self): + """Runs before each test.""" + if 'LANG' in iter(os.environ.keys()): + os.environ.__delitem__('LANG') + + def tearDown(self): + """Runs after each test.""" + if 'LANG' in iter(os.environ.keys()): + os.environ.__delitem__('LANG') + + def test_qgis_translations(self): + """Test that translations work.""" + parent_path = os.path.join(__file__, os.path.pardir, os.path.pardir) + dir_path = os.path.abspath(parent_path) + file_path = os.path.join( + dir_path, 'i18n', 'af.qm') + translator = QTranslator() + translator.load(file_path) + QCoreApplication.installTranslator(translator) + + expected_message = 'Goeie more' + real_message = QCoreApplication.translate("@default", 'Good morning') + self.assertEqual(real_message, expected_message) + + +if __name__ == "__main__": + suite = unittest.makeSuite(SafeTranslationsTest) + runner = unittest.TextTestRunner(verbosity=2) + runner.run(suite) diff --git a/QGIS_plugin/ecmwf_basins/test/utilities.py b/QGIS_plugin/ecmwf_basins/test/utilities.py new file mode 100644 index 0000000..be7ee3b --- /dev/null +++ b/QGIS_plugin/ecmwf_basins/test/utilities.py @@ -0,0 +1,61 @@ +# coding=utf-8 +"""Common functionality used by regression tests.""" + +import sys +import logging + + +LOGGER = logging.getLogger('QGIS') +QGIS_APP = None # Static variable used to hold hand to running QGIS app +CANVAS = None +PARENT = None +IFACE = None + + +def get_qgis_app(): + """ Start one QGIS application to test against. + + :returns: Handle to QGIS app, canvas, iface and parent. If there are any + errors the tuple members will be returned as None. + :rtype: (QgsApplication, CANVAS, IFACE, PARENT) + + If QGIS is already running the handle to that app will be returned. + """ + + try: + from qgis.PyQt import QtGui, QtCore + from qgis.core import QgsApplication + from qgis.gui import QgsMapCanvas + from .qgis_interface import QgisInterface + except ImportError: + return None, None, None, None + + global QGIS_APP # pylint: disable=W0603 + + if QGIS_APP is None: + gui_flag = True # All test will run qgis in gui mode + #noinspection PyPep8Naming + QGIS_APP = QgsApplication(sys.argv, gui_flag) + # Make sure QGIS_PREFIX_PATH is set in your env if needed! + QGIS_APP.initQgis() + s = QGIS_APP.showSettings() + LOGGER.debug(s) + + global PARENT # pylint: disable=W0603 + if PARENT is None: + #noinspection PyPep8Naming + PARENT = QtGui.QWidget() + + global CANVAS # pylint: disable=W0603 + if CANVAS is None: + #noinspection PyPep8Naming + CANVAS = QgsMapCanvas(PARENT) + CANVAS.resize(QtCore.QSize(400, 400)) + + global IFACE # pylint: disable=W0603 + if IFACE is None: + # QgisInterface is a stub implementation of the QGIS plugin interface + #noinspection PyPep8Naming + IFACE = QgisInterface(CANVAS) + + return QGIS_APP, CANVAS, IFACE, PARENT From 12d01a99e54041cb5677b04f544de6651288142e Mon Sep 17 00:00:00 2001 From: Markus Metz Date: Wed, 28 Aug 2019 14:51:54 +0200 Subject: [PATCH 05/12] ecmwf_gridpoints fix file name and class names --- QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints.py | 4 ++++ ...f_basins_dialog.py => test_ecmwf_gridpoints_dialog.py} | 8 ++++---- QGIS_plugin/ecmwf_gridpoints/test/test_resources.py | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) rename QGIS_plugin/ecmwf_gridpoints/test/{test_ecmwf_basins_dialog.py => test_ecmwf_gridpoints_dialog.py} (86%) diff --git a/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints.py b/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints.py index 6c6a5b7..aa85a9f 100644 --- a/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints.py +++ b/QGIS_plugin/ecmwf_gridpoints/ecmwf_gridpoints.py @@ -307,6 +307,10 @@ def run(self): print("GRASS command:") print("%s" % gcommands) + self.iface.messageBar().pushMessage( + "Start", "Updating gridpoints, please wait...", + level=Qgis.Info) + Grass7Utils.executeGrass([gcommands], feedback) Grass7Utils.endGrassSession() diff --git a/QGIS_plugin/ecmwf_gridpoints/test/test_ecmwf_basins_dialog.py b/QGIS_plugin/ecmwf_gridpoints/test/test_ecmwf_gridpoints_dialog.py similarity index 86% rename from QGIS_plugin/ecmwf_gridpoints/test/test_ecmwf_basins_dialog.py rename to QGIS_plugin/ecmwf_gridpoints/test/test_ecmwf_gridpoints_dialog.py index f879360..258ee31 100644 --- a/QGIS_plugin/ecmwf_gridpoints/test/test_ecmwf_basins_dialog.py +++ b/QGIS_plugin/ecmwf_gridpoints/test/test_ecmwf_gridpoints_dialog.py @@ -16,18 +16,18 @@ from qgis.PyQt.QtGui import QDialogButtonBox, QDialog -from ecmwf_basins_dialog import BasinsDialog +from ecmwf_gridpoints_dialog import GridpointsDialog from utilities import get_qgis_app QGIS_APP = get_qgis_app() -class BasinsDialogTest(unittest.TestCase): +class GridpointsDialogTest(unittest.TestCase): """Test dialog works.""" def setUp(self): """Runs before each test.""" - self.dialog = BasinsDialog(None) + self.dialog = GridpointsDialog(None) def tearDown(self): """Runs after each test.""" @@ -49,7 +49,7 @@ def test_dialog_cancel(self): self.assertEqual(result, QDialog.Rejected) if __name__ == "__main__": - suite = unittest.makeSuite(BasinsDialogTest) + suite = unittest.makeSuite(GridpointsDialogTest) runner = unittest.TextTestRunner(verbosity=2) runner.run(suite) diff --git a/QGIS_plugin/ecmwf_gridpoints/test/test_resources.py b/QGIS_plugin/ecmwf_gridpoints/test/test_resources.py index 2969d06..da5d029 100644 --- a/QGIS_plugin/ecmwf_gridpoints/test/test_resources.py +++ b/QGIS_plugin/ecmwf_gridpoints/test/test_resources.py @@ -18,7 +18,7 @@ -class BasinsDialogTest(unittest.TestCase): +class GridpointsDialogTest(unittest.TestCase): """Test rerources work.""" def setUp(self): @@ -31,12 +31,12 @@ def tearDown(self): def test_icon_png(self): """Test we can click OK.""" - path = ':/plugins/Basins/icon.png' + path = ':/plugins/Gridpoints/icon.png' icon = QIcon(path) self.assertFalse(icon.isNull()) if __name__ == "__main__": - suite = unittest.makeSuite(BasinsResourcesTest) + suite = unittest.makeSuite(GridpointsResourcesTest) runner = unittest.TextTestRunner(verbosity=2) runner.run(suite) From f653972de5b504ed6453276f46d29a8d213abcbd Mon Sep 17 00:00:00 2001 From: Markus Metz Date: Wed, 28 Aug 2019 14:54:45 +0200 Subject: [PATCH 06/12] ecmwf_addregion add message --- QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool.py b/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool.py index d28bdf2..8b38f34 100644 --- a/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool.py +++ b/QGIS_plugin/ecmwf_dissolve_tool/ecmwf_dissolve_tool.py @@ -306,6 +306,10 @@ def run(self): print("GRASS command:") print("%s" % gcommands) + self.iface.messageBar().pushMessage( + "Start", "Adding the new partner region, please wait...", + level=Qgis.Info) + Grass7Utils.executeGrass([gcommands], feedback) Grass7Utils.endGrassSession() From 78feff42c066a4e14d5b539a22e537a583fc6dd4 Mon Sep 17 00:00:00 2001 From: Markus Metz Date: Wed, 28 Aug 2019 14:55:19 +0200 Subject: [PATCH 07/12] ecmwf_basins add message --- QGIS_plugin/ecmwf_basins/ecmwf_basins.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/QGIS_plugin/ecmwf_basins/ecmwf_basins.py b/QGIS_plugin/ecmwf_basins/ecmwf_basins.py index c524113..027c5c2 100644 --- a/QGIS_plugin/ecmwf_basins/ecmwf_basins.py +++ b/QGIS_plugin/ecmwf_basins/ecmwf_basins.py @@ -297,6 +297,10 @@ def run(self): print("GRASS command:") print("%s" % gcommands) + self.iface.messageBar().pushMessage( + "Start", "Updating river basins, please wait...", + level=Qgis.Info) + Grass7Utils.executeGrass([gcommands], feedback) Grass7Utils.endGrassSession() From f56baa526f57f7bd70485dbc629e121c16615b4c Mon Sep 17 00:00:00 2001 From: Markus Metz Date: Wed, 28 Aug 2019 21:22:38 +0200 Subject: [PATCH 08/12] QGIS plugins change name of plugins --- QGIS_plugin/ecmwf_gridpoints/metadata.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGIS_plugin/ecmwf_gridpoints/metadata.txt b/QGIS_plugin/ecmwf_gridpoints/metadata.txt index 117c03e..1a2fdf2 100644 --- a/QGIS_plugin/ecmwf_gridpoints/metadata.txt +++ b/QGIS_plugin/ecmwf_gridpoints/metadata.txt @@ -3,7 +3,7 @@ # This file should be included when you package your plugin.# Mandatory items: [general] -name=ECMWF gridpoints +name=ECMWF update gridpoints qgisMinimumVersion=3.0 description=This plugin updates ECMWF grid points version=0.1 From cf7fdcc124c398f507998e6f88299fe91aee040f Mon Sep 17 00:00:00 2001 From: Markus Metz Date: Wed, 28 Aug 2019 21:23:45 +0200 Subject: [PATCH 09/12] QGIS plugins change name of plugins --- QGIS_plugin/ecmwf_basins/metadata.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGIS_plugin/ecmwf_basins/metadata.txt b/QGIS_plugin/ecmwf_basins/metadata.txt index 564b763..af29cd4 100644 --- a/QGIS_plugin/ecmwf_basins/metadata.txt +++ b/QGIS_plugin/ecmwf_basins/metadata.txt @@ -3,7 +3,7 @@ # This file should be included when you package your plugin.# Mandatory items: [general] -name=ECMWF basins +name=ECMWF update basins qgisMinimumVersion=3.0 description=This plugin updates ECMWF basins version=0.1 From 05dc16c97b9dcc5fb89c5364607d768b30594d07 Mon Sep 17 00:00:00 2001 From: Markus Metz Date: Wed, 28 Aug 2019 22:17:45 +0200 Subject: [PATCH 10/12] ecmwf_basins fix window title --- QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog_base.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog_base.ui b/QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog_base.ui index ba6625a..845f1dc 100644 --- a/QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog_base.ui +++ b/QGIS_plugin/ecmwf_basins/ecmwf_basins_dialog_base.ui @@ -11,7 +11,7 @@
- ECMWF grid points + ECMWF basins From 22619e64b70bff365de59ecfb8df324aa4fd56f5 Mon Sep 17 00:00:00 2001 From: Markus Metz Date: Wed, 28 Aug 2019 22:35:01 +0200 Subject: [PATCH 11/12] Documentation for the GRASS GIS addons and the QGIS plugins --- ESoWC 2019 Dissolve tool.odt | Bin 0 -> 20039 bytes ESoWC 2019 Dissolve tool.pdf | Bin 0 -> 60826 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 ESoWC 2019 Dissolve tool.odt create mode 100644 ESoWC 2019 Dissolve tool.pdf diff --git a/ESoWC 2019 Dissolve tool.odt b/ESoWC 2019 Dissolve tool.odt new file mode 100644 index 0000000000000000000000000000000000000000..218319be3dbcc4ac4d131fefd120b3961a111308 GIT binary patch literal 20039 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 bjCV=(R;K$l83400RtS*2nXVE{nBV>eff#Hr literal 0 HcmV?d00001 diff --git a/ESoWC 2019 Dissolve tool.pdf b/ESoWC 2019 Dissolve tool.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9455dfbaff1ce28c70fb90bfcc2bcb10e0eb17b8 GIT binary patch literal 60826 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�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 Date: Wed, 4 Sep 2019 14:47:37 +0200 Subject: [PATCH 12/12] ESoWC dissolve tool updated manual --- ESoWC 2019 Dissolve tool.odt | Bin 20039 -> 21970 bytes ESoWC 2019 Dissolve tool.pdf | Bin 60826 -> 63355 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/ESoWC 2019 Dissolve tool.odt b/ESoWC 2019 Dissolve tool.odt index 218319be3dbcc4ac4d131fefd120b3961a111308..82508ddc3f84d177d8cfbbf8edcd3478c2079382 100644 GIT binary patch delta 19934 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 QpgKRY&N(q=?Te@X0HSh@C;$Ke delta 17977 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 diff --git a/ESoWC 2019 Dissolve tool.pdf b/ESoWC 2019 Dissolve tool.pdf index 9455dfbaff1ce28c70fb90bfcc2bcb10e0eb17b8..d6ba5229fe319ba68c5b9f67717ea65e827667ee 100644 GIT binary patch delta 29969 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 A%K!iX