diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..0b1e1e7
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,27 @@
+**/__pycache__
+**/.venv
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/bin
+**/charts
+**/docker-compose*
+**/compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml
index da972f0..c9c005b 100644
--- a/.github/workflows/on-pull-request.yml
+++ b/.github/workflows/on-pull-request.yml
@@ -15,3 +15,5 @@ jobs:
python-version: "3.13"
- name: flake8 Lint
uses: py-actions/flake8@v2
+ with:
+ path: plugin_code/
diff --git a/.gitignore b/.gitignore
index a05ebcc..3edd7db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
__pycache__/
*.py[cod]
*$py.class
+docker/qgis_plugins/*
# C extensions
*.so
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 7f1ecf1..aad5a69 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,67 +1,20 @@
{
- "version": "0.2.0",
- "configurations": [
- {
- "name": "Python: Current File (Integrated Terminal)",
- "type": "python",
- "request": "launch",
- "program": "${file}",
- "console": "integratedTerminal"
- },
- {
- "name": "Python: Remote Attach",
- "type": "python",
- "request": "attach",
- "port": 5678,
- "host": "localhost",
- "pathMappings": [
- {
- "localRoot": "${workspaceFolder}",
- "remoteRoot": "${workspaceFolder}"
- }
- ]
- },
- {
- "name": "Python: Module",
- "type": "python",
- "request": "launch",
- "module": "enter-your-module-name-here",
- "console": "integratedTerminal"
- },
- {
- "name": "Python: Django",
- "type": "python",
- "request": "launch",
- "program": "${workspaceFolder}/manage.py",
- "console": "integratedTerminal",
- "args": [
- "runserver",
- "--noreload",
- "--nothreading"
- ],
- "django": true
- },
- {
- "name": "Python: Flask",
- "type": "python",
- "request": "launch",
- "module": "flask",
- "env": {
- "FLASK_APP": "app.py"
- },
- "args": [
- "run",
- "--no-debugger",
- "--no-reload"
- ],
- "jinja": true
- },
- {
- "name": "Python: Current File (External Terminal)",
- "type": "python",
- "request": "launch",
- "program": "${file}",
- "console": "externalTerminal"
- }
- ]
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Python: Remote Attach",
+ "type": "python",
+ "request": "attach",
+ "connect": {
+ "host": "localhost",
+ "port": 5678
+ },
+ "pathMappings": [
+ {
+ "localRoot": "${workspaceFolder}/plugin_code",
+ "remoteRoot": "/root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/qgis-shogun-editor"
+ }
+ ]
+ }
+ ]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 615aafb..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "python.pythonPath": "/usr/bin/python3"
-}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..585abd4
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,23 @@
+# For more information, please refer to https://aka.ms/vscode-docker-python
+FROM python:3-slim
+
+# Keeps Python from generating .pyc files in the container
+ENV PYTHONDONTWRITEBYTECODE=1
+
+# Turns off buffering for easier container logging
+ENV PYTHONUNBUFFERED=1
+
+# Install pip requirements
+COPY requirements.txt .
+RUN python -m pip install -r requirements.txt
+
+WORKDIR /app
+COPY . /app
+
+# Creates a non-root user with an explicit UID and adds permission to access the /app folder
+# For more info, please refer to https://aka.ms/vscode-docker-python-configure-containers
+RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app
+USER appuser
+
+# During debugging, this entry point will be overridden. For more information, please refer to https://aka.ms/vscode-docker-python-debug
+CMD ["python", "plugin_code/resources.py"]
diff --git a/LICENSE b/LICENSE
index 1d3d68c..a3d1140 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,5 @@
-Shogun Qgis Configurator
-Copyright (C) 2025 terrestris GmbH & Co. KG
+Qgis-Shogun-Editor
+Copyright (C) 2024 terrestris GmbH & Co. KG
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
diff --git a/README.html b/README.html
deleted file mode 100644
index ed57e4b..0000000
--- a/README.html
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-Plugin Builder Results
-
-Congratulations! You just built a plugin for QGIS!
-
-
-Your plugin
GeoportalRlpMetadataSearch was created in:
-
/home/armin/GDI-RP/devel/qgis/gprlp_metadata_search
-
-Your QGIS plugin directory is located at:
- /home/armin/.local/share/QGIS/QGIS3/profiles/default/python/plugins
-
-
What's Next
-
- 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)
- Optionally, test the generated sources using make test (or run tests from your IDE)
- Copy the entire directory containing your new plugin to the QGIS plugin directory (see Notes below)
- Test the plugin by enabling it in the QGIS plugin manager
- Customize it by editing the implementation file gprlp_metadata_search.py
- Create your own custom icon, replacing the default icon.png
- Modify your user interface by opening gprlp_metadata_search_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/README.md b/README.md
index 1cfba2c..eed0726 100644
--- a/README.md
+++ b/README.md
@@ -1,59 +1,17 @@
+# qgis-shogun-editor
-# Menu
-Nach der Installation findet man das Plugin unter dem Menupunkt **Web** und in der **Erweiterungswerkzeugleiste**
+WIP: reworking the old plugin
-
+## Development
-After the installation you will find the Plugin starter under **Web** and in the **Plugins Toolbar**
+### Docker
-
+**Start**
+Run
-# gprlp_metadata_search
-QGIS Plugin zur Abfrage der GeoPortal.rlp Metadaten-Suchschnittstelle (https://documents.geoportal.rlp.de/mediawiki/index.php/SearchInterface). Man kann nach registrierten Resourcen suchen und gekoppelte Dienste direkt in den QGIS Browser laden. Neben der Suche im GeoPortal.rlp, kann man auch in den dort registrierten Katalogen suchen. Diese Suche geht über den json-Wrapper der jeweiligen Katalogschnittstelle und löst ebenfalls die Daten-Service Kopplung auf. Die im der jeweiligen GeoPortal.rlp Instanz registrierten Katalogschnittstellen sind als json über folgende URL verfügbar: https://www.geoportal.rlp.de/mapbender/php/mod_showCswList.php.
-Aktuell stehen im Geoportal des Landes Rheinland-Pfalz sowohl der deutsche, als auch der europäische Geodatenkatalog zur Verfügung. **Ein Nutzer hat damit Zugriff auf alle Geodaten der GDI-DE sowie von INSPIRE!** Die Auflösung der Daten-Service Kopplung erfolgt gemäß den Vorgaben der EU INSPIRE-Richtlinie: https://github.com/INSPIRE-MIF/technical-guidelines/blob/2022.1/metadata/metadata-iso19139/metadata-iso19139.adoc#4124-linking-to-provided-data-sets-using-coupled-resource
+```shell
+./setup.sh
+```
-
-
-
-QGIS plugin for using the GeoPortal.rlp RESTful metadata SearchInterface (https://documents.geoportal.rlp.de/mediawiki/index.php/SearchInterface) to search and load remote data. There is also another similar interface for a simple remote search in registrated CSW. The registrated CSW interfaces are available in json format: https://www.geoportal.rlp.de/mapbender/php/mod_showCswList.php.
-**Actually a user get a direct access to the central spatial data catalogue of Germany (Geodatenkatalog.DE) and the european INSPIRE catalogue!** The plugin allows searching for datasets and it automatically resolves the coupled services for this datasets while using the european - INSPIRE - way of doing this (https://github.com/INSPIRE-MIF/technical-guidelines/blob/2022.1/metadata/metadata-iso19139/metadata-iso19139.adoc).
-
-
-# Video
-
-
-# Optionen / Options
-## Suchressourcen / Search resources
-### Datensätze / Datasets
-Hier wird nach dem Ressourcentyp "dataset" gesucht. Nach der Selektion eines Datensatztitles in der Trefferliste, werden weitere Metadaten zum Treffer angezeigt. Unter "Weitere Infos" werden die zugehörigen Zugriffsmöglichkeiten über WMS, WFS, ATOM Feeds sowie OGC API Features aufgelistet. Die Selektion einer Zugriffsmöglichkeit zeigt Metadaten zu dieser Option und erlaubt es den Webservice direkt in den QGIS Browser zu übernehmen (WMS / WFS / OGC API Features).
-
-
-
-Search for "dataset". After the selection of the title in the resultlist, some more metadata about the dataset is shown. Under "Further info" the coupled accessoptions via WMS, WFS, ATOM Feed and OGC API Features are shown. The selection of one of this accessoptions give some further metadata about the service and allows to load it directly into QGIS browser (WMS / WFS / OGC API Features).
-
-### Kartenebenen / Map layers
-Hier werden alle WMS Layer durchsucht. Ähnlich wie bei der Datensatzsuche erhält man bei Auswahl eines Treffers weitere Metadaten zum jeweiligen Layer und die Möglichkeit ihn direkt in den QGIS Browser zu übernehmen.
-
-Search for WMS layer. Similar to dataset search, the user gets more metadata about the corresponding layer after its selection. The selected layer can be loaded directly into the QGIS browser.
-
-### Publizierte OWS Context Dokumenten / Published OWS Context Documents
-Im GeoPortal.rlp werden auch Metadaten zu Kartenzusammenstellungen in Form von OGC WMC Dokumenten verwaltet. Diese Ressourcen werden können publiziert werden, um einen speziellen thematischen Anwendungsfall abzudecken. Die Kartenzusammenstellungen können auch in Form von OWS Context Dokumenten (https://portal.ogc.org/files/?artifact_id=68826) exportiert werden. Das Plugin kann diese Laden und vorhandene WMS Layer (offerings) in den QGIS Browser zu übernehmen. Weitere Informationen zum OWS Context Standard: http://www.owscontext.org/owc_user_guide/C0_userGuide.html
-
-The GeoPortal.rlp registry mangages metadata for user-defined context documents (actual extended OGC WMC 1.1.0 documents). This resources can be published to serve a special theme. The context documents are also available in form of OWS Context documents (json encoding - https://portal.ogc.org/files/?artifact_id=68826). The plugin can read this documents and allows the loading of defined WMS offerings. Further information about OWS Context: http://www.owscontext.org/owc_user_guide/C0_userGuide.html
-
-### Entfernte CSW / Remote CSW
-Siehe Beschreibung des Plugins zu Begin.
-
-See plugin description at the begin.
-
-#### Geodatenkatalog.DE / German spatial data catalogue
-
-#### INSPIRE Katalog / INSPIRE catalogue
-
-
-## Wechsel zwischen Länderkatalogen / Switch between federal catalogues
-### Rheinland-Pfalz / Rhineland-Palatinate
-### Hessen / Hesse
-### Saarland / Saarland
+to start QGIS.
diff --git a/README.txt b/README.txt
deleted file mode 100644
index 8b52f4f..0000000
--- a/README.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-Plugin Builder Results
-
-Your plugin GeoportalRlpMetadataSearch was created in:
- /home/armin/GDI-RP/devel/qgis/gprlp_metadata_search
-
-Your QGIS plugin directory is located at:
- /home/armin/.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: ``gprlp_metadata_search.py``
-
- * Create your own custom icon, replacing the default icon.png
-
- * Modify your user interface by opening GeoportalRlpMetadataSearch_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/connection/networkaccessmanager.py b/connection/networkaccessmanager.py
deleted file mode 100644
index d87a55b..0000000
--- a/connection/networkaccessmanager.py
+++ /dev/null
@@ -1,343 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-***************************************************************************
- An httplib2 replacement that uses QgsNetworkAccessManager
-
- ---------------------
- Date : August 2016
- Copyright : (C) 2016 Boundless, http://boundlessgeo.com
- Email : apasotti at boundlessgeo dot com
-***************************************************************************
-* *
-* 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. *
-* *
-***************************************************************************
-
-Minor changes by J. Grieb (jgrieb (at) terrestris.de) in 2018 for the
-shogun-editor plugin by terrestris GmbH & Co. KG (https://www.terrestris.de/en/)
--> for re-using purposes better use the original code
-"""
-
-
-import sys
-import urllib
-
-from qgis.core import QgsAuthManager, QgsMessageLog, QgsNetworkAccessManager
-from qgis.PyQt.QtCore import QEventLoop, QUrl
-from qgis.PyQt.QtNetwork import QNetworkReply, QNetworkRequest
-
-__author__ = "Alessandro Pasotti"
-__date__ = "August 2016"
-
-# FIXME: ignored
-DEFAULT_MAX_REDIRECTS = 4
-
-
-class RequestsException(Exception):
- pass
-
-
-class RequestsExceptionTimeout(RequestsException):
- pass
-
-
-class RequestsExceptionConnectionError(RequestsException):
- pass
-
-
-class Map(dict):
- """
- Example:
- m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
- """
-
- def __init__(self, *args, **kwargs):
- super(Map, self).__init__(*args, **kwargs)
- for arg in args:
- if isinstance(arg, dict):
- for k, v in arg.items():
- self[k] = v
-
- if kwargs:
- for k, v in kwargs.items():
- self[k] = v
-
- def __getattr__(self, attr):
- return self.get(attr)
-
- def __setattr__(self, key, value):
- self.__setitem__(key, value)
-
- def __setitem__(self, key, value):
- super(Map, self).__setitem__(key, value)
- self.__dict__.update({key: value})
-
- def __delattr__(self, item):
- self.__delitem__(item)
-
- def __delitem__(self, key):
- super(Map, self).__delitem__(key)
- del self.__dict__[key]
-
-
-class Response(Map):
- pass
-
-
-PYTHON_VERSION = sys.version_info[0]
-
-
-class NetworkAccessManager:
- """
- This class mimicks httplib2 by using QgsNetworkAccessManager for all
- network calls.
-
- The return value is a tuple of (response, content), the first being and
- instance of the Response class, the second being a string that contains
- the response entity body.
-
- Parameters
- ----------
- debug : bool
- verbose logging if True
- exception_class : Exception
- Custom exception class
-
- Usage
- -----
- ::
- nam = NetworkAccessManager(authcgf)
- try:
- (response, content) = nam.request('http://www.example.com')
- except RequestsException, e:
- # Handle exception
- pass
-
-
- """
-
- def __init__(
- self,
- authid=None,
- disable_ssl_certificate_validation=False,
- exception_class=None,
- debug=True,
- ):
- self.disable_ssl_certificate_validation = disable_ssl_certificate_validation
- self.authid = authid
- self.reply = None
- self.debug = debug
- self.exception_class = exception_class
- self.cookie = None
- self.basicauth = None
-
- def setBasicauth(self, encodedString):
- self.basicauth = encodedString
-
- def setCookie(self, cookie):
- self.cookie = cookie
-
- def msg_log(self, msg):
- if self.debug:
- QgsMessageLog.logMessage(msg, "NetworkAccessManager")
-
- def request(
- self,
- url,
- method="GET",
- body=None,
- headers=None,
- redirections=DEFAULT_MAX_REDIRECTS,
- connection_type=None,
- authenticate=True,
- ):
- """
- Make a network request by calling QgsNetworkAccessManager.
- redirections argument is ignored and is here only for httplib2 compatibility.
- """
- self.msg_log("http_call request: {0}".format(url))
- self.http_call_result = Response(
- {
- "status": 0,
- "status_code": 0,
- "status_message": "",
- "text": "",
- "ok": False,
- "headers": {},
- "reason": "",
- "exception": None,
- }
- )
- req = QNetworkRequest()
- req.setAttribute(
- QNetworkRequest.CookieSaveControlAttribute, QNetworkRequest.Manual
- )
- req.setAttribute(
- QNetworkRequest.CookieLoadControlAttribute, QNetworkRequest.Manual
- )
- # Avoid double quoting form QUrl
- url = urllib.parse.unquote(url)
- req.setUrl(QUrl(url))
-
- if self.cookie is not None:
- if headers is not None:
- headers["Cookie"] = self.cookie
- else:
- headers = {"Cookie": self.cookie}
-
- if self.basicauth is not None and authenticate:
- if headers is not None:
- headers["Authorization"] = self.basicauth
- else:
- headers = {"Authorization": self.basicauth}
-
- if headers is not None:
- # This fixes a wierd error with compressed content not being correctly
- # inflated.
- # If you set the header on the QNetworkRequest you are basically telling
- # QNetworkAccessManager "I know what I'm doing, please don't do any content
- # encoding processing".
- # See: https://bugs.webkit.org/show_bug.cgi?id=63696#c1
- try:
- del headers["Accept-Encoding"]
- except KeyError:
- pass
- for k, v in headers.items():
- if PYTHON_VERSION >= 3:
- if isinstance(k, str):
- k = k.encode("utf-8")
- if isinstance(v, str):
- v = v.encode("utf-8")
- req.setRawHeader(k, v)
-
- if self.authid:
- self.msg_log("Update request w/ authid: {0}".format(self.authid))
- QgsAuthManager.instance().updateNetworkRequest(req, self.authid)
- if self.reply is not None and self.reply.isRunning():
- self.reply.close()
- if method.lower() == "delete":
- func = getattr(QgsNetworkAccessManager.instance(), "deleteResource")
- else:
- func = getattr(QgsNetworkAccessManager.instance(), method.lower())
- # Calling the server ...
- # Let's log the whole call for debugging purposes:
- self.msg_log(
- "Sending %s request to %s" % (method.upper(), req.url().toString())
- )
- headers = {str(h): str(req.rawHeader(h)) for h in req.rawHeaderList()}
- for k, v in headers.items():
- self.msg_log("%s: %s" % (k, v))
- if method.lower() in ["post", "put"]:
- if PYTHON_VERSION >= 3:
- if isinstance(body, str):
- body = body.encode("utf-8")
- self.reply = func(req, body)
- else:
- self.reply = func(req)
- if self.authid:
- self.msg_log("Update reply w/ authid: {0}".format(self.authid))
- QgsAuthManager.instance().updateNetworkReply(self.reply, self.authid)
-
- self.reply.sslErrors.connect(self.sslErrors)
- self.reply.finished.connect(self.replyFinished)
-
- # Call and block
- self.el = QEventLoop()
- self.reply.finished.connect(self.el.quit)
- self.reply.downloadProgress.connect(self.downloadProgress)
-
- # Catch all exceptions (and clean up requests)
- try:
- self.el.exec_()
- # Let's log the whole response for debugging purposes:
- self.msg_log(
- "Got response %s %s from %s"
- % (
- self.http_call_result.status_code,
- self.http_call_result.status_message,
- self.reply.url().toString(),
- )
- )
- headers = {
- str(h): str(self.reply.rawHeader(h)) for h in self.reply.rawHeaderList()
- }
- for k, v in headers.items():
- self.msg_log("%s: %s" % (k, v))
- if len(self.http_call_result.text) < 1024:
- self.msg_log("Payload :\n%s" % self.http_call_result.text)
- else:
- self.msg_log("Payload is > 1 KB ...")
- except Exception as e:
- raise e
- finally:
- if self.reply is not None:
- if self.reply.isRunning():
- self.reply.close()
- self.msg_log("Deleting reply ...")
- self.reply.deleteLater()
- self.reply = None
- else:
- self.msg_log("Reply was already deleted ...")
- if not self.http_call_result.ok:
- if self.http_call_result.exception and not self.exception_class:
- raise self.http_call_result.exception
- else:
- raise self.exception_class(self.http_call_result.reason)
- return (self.http_call_result, self.http_call_result.text)
-
- # @pyqtSlot()
- def downloadProgress(self, bytesReceived, bytesTotal):
- """Keep track of the download progress"""
- # self.msg_log("downloadProgress %s of %s ..." % (bytesReceived, bytesTotal))
- pass
-
- # @pyqtSlot()
- def replyFinished(self):
- err = self.reply.error()
- httpStatus = self.reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
- httpStatusMessage = self.reply.attribute(
- QNetworkRequest.HttpReasonPhraseAttribute
- )
- self.http_call_result.status_code = httpStatus
- self.http_call_result.status = httpStatus
- self.http_call_result.status_message = httpStatusMessage
- for k, v in self.reply.rawHeaderPairs():
- self.http_call_result.headers[str(k)] = str(v)
- self.http_call_result.headers[str(k).lower()] = str(v)
- if err != QNetworkReply.NoError:
- msg = "Network error #{0}: {1}".format(
- self.reply.error(), self.reply.errorString()
- )
- self.http_call_result.reason = msg
- self.http_call_result.ok = False
- self.msg_log(msg)
- if err == QNetworkReply.TimeoutError:
- self.http_call_result.exception = RequestsExceptionTimeout(msg)
- elif err == QNetworkReply.ConnectionRefusedError:
- self.http_call_result.exception = RequestsExceptionConnectionError(msg)
- else:
- self.http_call_result.exception = RequestsException(msg)
- else:
- # since Python 3 readAll() returns a PyQt5.QByteArray, we
- # want only the data
- if PYTHON_VERSION >= 3:
- self.http_call_result.text = self.reply.readAll().data().decode("utf-8")
- else:
- self.http_call_result.text = str(self.reply.readAll())
- self.http_call_result.ok = True
- self.reply.deleteLater()
-
- # @pyqtSlot()
- def sslErrors(self, reply, ssl_errors):
- """
- Handle SSL errors, logging them if debug is on and ignoring them
- if disable_ssl_certificate_validation is set.
- """
- if ssl_errors:
- for v in ssl_errors:
- self.msg_log("SSL Error: %s" % v)
- if self.disable_ssl_certificate_validation:
- reply.ignoreSslErrors()
diff --git a/connection/shogunressource.py b/connection/shogunressource.py
deleted file mode 100644
index 3102254..0000000
--- a/connection/shogunressource.py
+++ /dev/null
@@ -1,509 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-(c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/
- This code is licensed under the GPL 2.0 license.
-"""
-
-
-import json
-import os
-import sys
-import webbrowser
-from base64 import b64encode
-
-from qgis.core import QgsApplication
-from qgis.PyQt.QtCore import QFile, QIODevice, QSize
-from qgis.PyQt.QtGui import QIcon
-from qgis.PyQt.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest
-from qgis.PyQt.QtXml import QDomDocument
-
-from ..layerutils import createAndParseSld
-from .networkaccessmanager import (
- NetworkAccessManager,
- RequestsException,
- RequestsExceptionConnectionError,
-)
-
-PYTHON_VERSION = sys.version_info[0]
-
-__author__ = "ntreff"
-__date__ = "July 2025"
-
-
-class ShogunRessource:
- """This class controls all interactions between QGIS and the Shogun ressource,
- apart from creating wfs/wms layers (see layerutils for this). It makes use of the
- class NetworkAccessManager, which calls QgsNetworkAccessManager for all http
- requests"""
-
- def __init__(self, iface, url, name, user=None, pw=None):
- self.iface = iface
- if url.endswith("webapp"):
- url += "/"
- elif url.endswith("rest/"):
- url = url[:-5]
- elif url.endswith("rest"):
- url = url[:-4]
-
- self.baseurl = url # url should have the format: 'https:.../shogun2-webapp/'
- self.name = name
- self.applications = []
- self.layers = []
- self.mapconfigs = []
- self.extents = []
- self.http = NetworkAccessManager(debug=False)
-
- self.icondir = os.path.join(
- os.path.dirname(__file__), "..", "images", "custom-symbols"
- )
- if not os.path.isdir(self.icondir):
- os.makedirs(self.icondir)
-
- if user is not None and pw is not None:
- if PYTHON_VERSION >= 3:
- read_bytes = (user + ":" + pw).encode("utf-8")
- self.basicauth = b64encode(read_bytes)
- self.http.setBasicauth("Basic ".encode("utf-8") + self.basicauth)
- else:
- self.basicauth = b64encode(user + ":" + pw)
- self.http.setBasicauth("Basic " + self.basicauth)
-
- def checkConnection(self):
- # returning a tuple of ('true/false', 'message')
-
- # # TODO: here we check only the cases of http/https faults in the user's
- # input. Maybe there should be more error checks for in case the user has
- # written an url which is completely different (no http/ https at beginning)
-
- testurl = self.baseurl + "rest/applications"
- try:
- testresponse = self.http.request(testurl, method="HEAD")
- if testresponse[0]["status"] > 199 and testresponse[0]["status"] < 210:
- return (True, "")
- except RequestsException as e:
- print('Error during connection test: ' + str(e))
- if self.baseurl.startswith("https"):
- # if the first try with 'https' was not successful try http:
- testurl = "http" + testurl[5:]
- try:
- testresponse = self.http.request(testurl, method="HEAD")
- if testresponse[0]["status"] > 199 and testresponse[0]["status"] < 210:
- self.baseurl = "http" + self.baseurl[5:]
- return (True, "")
- except RequestsException as e:
- print('Could not fetch fetch applications ' + str(e))
- pass
- return (False, "Error : Failed to connect to the server")
-
- def userInfo(self, status, objectName, method):
- # method = 'created'/'updated'/'deleted'
- if status > 199 and status < 300:
- msg = objectName + " was successfully " + method
- self.iface.messageBar().pushSuccess("Shogun Editor Info:", msg)
- else:
- msg = "Error: " + objectName + " could not be " + method
- self.iface.messageBar().pushCritical("Shogun Editor Info:", msg)
-
- def editApplication(self, _id, data):
- data = json.dumps(data)
- url = self.baseurl + "rest/applications/" + str(_id)
- header = {"Content-type": "application/json"}
- response = self.http.request(url, method="PUT", body=data, headers=header)
- self.userInfo(response[0]["status"], "Application", "edited")
- return response[0]["status"]
-
- def editLayer(self, _id, data):
- data = json.dumps(data)
- url = self.baseurl + "rest/layers/" + str(_id)
- header = {"Content-type": "application/json"}
- response = self.http.request(url, method="PUT", body=data, headers=header)
- self.userInfo(response[0]["status"], "Layer", "edited")
- return response[0]["status"]
-
- def uploadNewApplication(self, data):
- # data as a json-like dict
- url = self.baseurl + "projectapps/create.action"
- header = {"Content-type": "application/json"}
- body = json.dumps(data)
- response = self.http.request(url, method="POST", body=body, headers=header)
- name = "Application " + data["name"]
- self.userInfo(response[0]["status"], name, "created")
- if response[0]["status"] == 200 or response[0]["status"] == 201:
- return True
- else:
- return False
-
- def editMapConfig(self, _id, data):
- # edit = None
- # for mapconfig in self.mapconfigs:
- # if mapconfig["id"] == _id:
- # edit = mapconfig
-
- url = self.baseurl + "mapconfigs/" + str(_id)
- body = (
- '{"id":' + str(_id) + ',"center":{"x":' + str(data["center"]["x"]) + ',"y":'
- )
- body += str(data["center"]["y"]) + '},"zoom":' + str(data["zoom"]) + "}"
- h = {"Content-type": "application/json"}
- response = self.http.request(url, method="PUT", body=body, headers=h)
- self.userInfo(response[0]["status"], "Homeview", "updated")
-
- def editObjectPermission(self, _id, objectType, permissionType, data):
- url = self.baseurl + "rest/entitypermission/" + objectType + "/" + str(_id)
- if permissionType == "User":
- url += "/ProjectUser"
- elif permissionType == "UserGroup":
- url += "/ProjectUserGroup"
- else:
- return
-
- header = {"Content-type": "application/json"}
- body = json.dumps(data)
- response = self.http.request(url, method="POST", body=body, headers=header)
- if response[0]["status"] == 200 or response[0]["status"] == 201:
- return True
- else:
- return False
-
- def updateData(self):
- try:
- self.updateApplications()
- self.updateLayers()
- self.updateExtentsAndMapConfigs()
- return True
- except RequestsExceptionConnectionError:
- self.iface.messageBar().pushCritical(
- "Connection Error:",
- "Could not connect to given SHOGUN host application - Please review url",
- )
- return False
-
- def updateApplications(self):
- url = self.baseurl + "rest/applications"
- response = self.http.request(url)
- self.applications = json.loads(response[1])
-
- def updateLayers(self):
- url = self.baseurl + "rest/layers"
- response = self.http.request(url)
- self.layers = json.loads(response[1])
-
- def updateSingleApplication(self, _id):
- url = self.baseurl + "rest/applications/" + str(_id)
- response = self.http.request(url)
- updatedApplication = json.loads(response[1])
- for app in enumerate(self.applications):
- if app[1]["id"] == _id:
- self.layers[app[0]] = updatedApplication
- return updatedApplication
-
- def updateSingleLayer(self, _id):
- url = self.baseurl + "rest/layers/" + str(_id)
- response = self.http.request(url)
- updatedLayer = json.loads(response[1])
- for layer in enumerate(self.layers):
- if layer[1]["id"] == _id:
- self.layers[layer[0]] = updatedLayer
- return updatedLayer
-
- # one method for retrieving user and groups permissions (permissionType)
- # for layers or applications (objectType)
- def getObjectPermissions(self, _id, objectType, permissionType):
- url = self.baseurl + "rest/entitypermission/Project" + objectType
- # objectType = 'Application' or 'Layer'
- # permissionType = 'User' or 'UserGroup'
- url += "/" + str(_id) + "/Project" + permissionType + "?"
- response = self.http.request(url)
- return json.loads(response[1])
-
- def updateExtentsAndMapConfigs(self):
- url = self.baseurl + "rest/extents"
- response = self.http.request(url)
- self.extents = json.loads(response[1])
- url = self.baseurl + "rest/mapconfigs"
- response = self.http.request(url)
- self.mapconfigs = json.loads(response[1])
-
- def getHomeviewByIds(self, mapconfigid, extent_id):
- homeview = {}
- for mapcf in self.mapconfigs:
- if mapcf["id"] == mapconfigid:
- homeview["mapconfig"] = mapcf
- for ext in self.extents:
- if ext["id"] == extent_id:
- homeview["extent"] = ext
- return homeview
-
- def getApplicationIdsAndNames(self, reload=False):
- if reload:
- self.updateApplications()
- return [(x["id"], x["name"]) for x in self.applications]
-
- def getLayerIdsAndNames(self, reload=False):
- if reload:
- self.updateLayers()
- return [(x["id"], x["name"], x["dataType"], x["source"]) for x in self.layers]
-
- def getApplicationAttrsById(self, _id):
- for x in self.applications:
- if x["id"] == _id:
- return x
-
- def getLayerAttrsById(self, _id):
- for x in self.layers:
- if x["id"] == _id:
- return x
-
- def getGroupNames(self):
- return [x["name"] for x in self.groups]
-
- def getUserNames(self):
- return [(x["lastName"] + ", " + x["firstName"]) for x in self.users]
-
- def downloadStyle(self, qgisLayerItem):
- # this downloads the layer's style, saves it as an sld and returns two
- # things: 1. the path to the .sld, 2. the specific name of the style
- # as it is saved in the background geoserver from shogun, obtained from
- # the xml-node sld:UserStyle - sld:Name
- # the specific name is later needed for re-uploading the edited style
-
- # TODO:
- # maybe a better implementation would be to pass the QDomDocument directly
- # to the layer and set it's style from sld...
-
- # unfortunately for a not known reason this does not work and we first
- # have to save the sld to a file, then do layer.loadSldStyle(file)
- # can someone fix it?#
-
- shogunlayer = qgisLayerItem.parentShogunLayer
- url = shogunlayer.source["url"]
- if url.startswith("/shogun2-webapp"):
- url = self.baseurl.rstrip("/shogun2-webapp/rest/") + url
- url += "?service=WMS&request=GetStyles&version=1.1.1&layers="
- url += shogunlayer.source["layerNames"]
- response = self.http.request(url, authenticate=False)
-
- mydoc = QDomDocument()
- mydoc.setContent(response[1])
- root = mydoc.firstChildElement("sld:StyledLayerDescriptor")
- namedLayerNode = root.firstChildElement("sld:NamedLayer")
- userStyleNode = namedLayerNode.firstChildElement("sld:UserStyle")
- sldNameNode = userStyleNode.firstChildElement("sld:Name")
- geoServerStyleName = sldNameNode.text()
-
- # # TODO: implement more than point style
- # check if custom icons are used in the style:
-
- # # NOTE: this is the beginning of a larger implementation of
- # exchange of icons
- # problem is that shogun2 only serves png icons, and qgis needs
- # svg to turn them into a style
- if "" in response[1]:
- self.iface.messageBar().pushInfo(
- "Info",
- "The downloaded style for the current layer contains custom icons "
- "from SHOGUN, which only serves them as PNG pictures, but QGIS "
- "can only read SVG. Until this is fixed, you see a default QGIS "
- "style for the layer",
- )
-
- """
- featureTypeStyleNode = userStyleNode.firstChildElement('sld:FeatureTypeStyle')
- rules = featureTypeStyleNode.elementsByTagName('sld:Rule')
- for x in range(rules.length()):
- rule = rules.at(x).toElement()
- listOfGraphics = rule.elementsByTagName('sld:OnlineResource')
- for x in range(listOfGraphics.length()):
- graphicNode = listOfGraphics(x)
- attributes = graphicNode.attributes()
- url = attributes.namedItem('xlink:href').nodeValue()
- id = url.split('getThumbnail.action?id=')[1]
- iconPath = self.downloadIconThumbnail(id)
- """
-
- dirpath = os.path.dirname(__file__)
- filename = os.path.join(dirpath, "latest-symbology.sld")
- with open(filename, "w") as file:
- file.write(response[1])
- return filename, geoServerStyleName
-
- def uploadStyle(self, qgisLayerItem):
- url = self.baseurl.rstrip("rest/") + "/sld/update.action"
- h = {"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"}
-
- sld = createAndParseSld(qgisLayerItem)
- b = "sld=" + sld + "&sldName=" + qgisLayerItem.stylename + "&layerId="
- b += str(qgisLayerItem.parentShogunLayer.id)
- response = self.http.request(url, method="POST", body=b, headers=h)
- if json.loads(response[1])["success"]:
- return True
- else:
- return False
-
- def prepareIconForUpload(self, svgIconName):
- svgPaths = QgsApplication.svgPaths()
- svgIconPath = None
- for _dir in svgPaths:
- if os.path.isfile(os.path.join(_dir, svgIconName)):
- svgIconPath = os.path.join(_dir, svgIconName)
- if svgIconPath is None or not svgIconPath.endswith("svg"):
- return False
- name = os.path.splitext(svgIconName)[0]
- outputPath = os.path.join(self.icondir, "qgis-" + name + ".png")
- img = QIcon(svgIconPath).pixmap(QSize(100, 100)).toImage()
- if img.save(outputPath):
- newId = self.uploadImage(outputPath)
- if newId:
- iconUrl = self.baseurl + "projectimage/getThumbnail.action?id="
- iconUrl += str(newId)
- return iconUrl
- self.userInfo(400, "Custom symbology icon", "uploaded")
- return False
-
- # # NOTE: the following method is still not used:
- # def downloadIconThumbnail(self, _id):
- # iconPath = os.path.join(self.icondir, str(icon['_id']) + '.png')
- # if os.path.isfile(iconPath):
- # return iconPath
- # else:
- # url = self.baseurl + 'projectimage/getThumbnail.action?id=' + str(id)
- # urlretrieve(url, iconPath)
- # return iconPath
-
- def uploadImage(self, pathToImage):
- img = QFile(pathToImage)
- img.open(QIODevice.ReadOnly)
- imgPart = QHttpPart()
- txt = 'form-data; name="file"; filename="' + os.path.basename(pathToImage) + '"'
- imgPart.setHeader(QNetworkRequest.ContentDispositionHeader, txt)
- imgPart.setHeader(QNetworkRequest.ContentTypeHeader, "image/zip")
- imgPart.setBodyDevice(img)
-
- multiPart = QHttpMultiPart(QHttpMultiPart.FormDataType)
- multiPart.append(imgPart)
-
- url = self.baseurl + "projectimage/upload.action?"
-
- response = self.http.request(url, method="POST", body=multiPart)
- if response[0]["status"] > 199 and response[0]["status"] < 210:
- # if icon upload was successfull, server returns id of the new icon
- # in it's database
- return json.loads(response[1])["data"]["id"]
- else:
- return False
-
- def publishWmsLayer(self, wmsUri):
- url = self.baseurl + "/ogcservicehelper/publishlayer.action?"
- url += wmsUri
- # header = {'Cookie': self.cookie}
- response = self.http.request(url, method="GET")
- self.userInfo(response[0]["status"], "New WMS layer", "published")
-
- def uploadLayer(self, pathToZipFile, dataType):
- url = self.baseurl + "/import/create-layer.action"
-
- # the following creates a QHttpMultiPart with 2 parts, one defining the
- # dataType (i.e. Vector or Raster) and one with the binary file data itself
-
- textpart = QHttpPart()
- textpart.setHeader(
- QNetworkRequest.ContentDispositionHeader, 'form-data; name="dataType"'
- )
- if PYTHON_VERSION >= 3:
- textpart.setBody(dataType.encode("utf-8"))
- else:
- textpart.setBody(dataType)
-
- file = QFile(pathToZipFile)
- file.open(QIODevice.ReadOnly)
- lyr = (
- 'form-data; name="file"; filename="' + os.path.basename(pathToZipFile) + '"'
- )
- layerpart = QHttpPart()
- layerpart.setHeader(QNetworkRequest.ContentTypeHeader, "application/zip")
- layerpart.setHeader(QNetworkRequest.ContentDispositionHeader, lyr)
- layerpart.setBodyDevice(file)
-
- multipart = QHttpMultiPart(QHttpMultiPart.FormDataType)
- multipart.append(textpart)
- multipart.append(layerpart)
-
- response = self.http.request(url, method="POST", body=multipart)
- res = json.loads(response[1])
- if res["success"]:
- self.userInfo(response[0]["status"], "New Vector Layer", "uploaded")
- else:
- if res["error"] == "NO_CRS":
- self.requestCrsUpdateOnLayer(res["importJobId"])
- else:
- self.userInfo(response[0]["status"], "New Vector Layer", "uploaded")
- return response[0]["status"]
-
- def requestCrsUpdateOnLayer(self, importJobId):
- url = self.baseurl + "/import/update-crs-for-import.action"
- data = (
- "importJobId=" +
- str(importJobId) +
- "&taskId=0&fileProjection=EPSG%3A3857&layerName=&dataType=Vector"
- )
- h = {"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"}
- self.http.request(url, method="POST", body=data, headers=h)
-
- def getFieldNamesFromWfs(self, layerRessourceName):
- url = (
- self.baseurl +
- "geoserver-noauth.action?service=WFS&request=DescribeFeatureType&typeName="
- )
- url += layerRessourceName + "&outputFormat=application/json"
- response = self.http.request(url)
- if response[0]["status"] == 200 or response[0]["status"] == 201:
- fieldNames = []
- answer = json.loads(response[1])
- if answer["featureTypes"][0]["properties"]:
- for prop in answer["featureTypes"][0]["properties"]:
- fieldNames.append(prop["name"])
- if len(fieldNames) > 0:
- return fieldNames
- return False
-
- def deleteLayer(self, _id):
- url = self.baseurl + "rest/projectlayers/" + str(_id)
- response = self.http.request(url, method="DELETE")
- self.userInfo(response[0]["status"], "Layer", "deleted")
-
- def deleteApplication(self, _id):
- url = self.baseurl + "rest/projectapps/" + str(_id)
- response = self.http.request(url, method="DELETE")
- self.userInfo(response[0]["status"], "Application", "deleted")
-
- def copyApplication(self, _id, applicationName):
- url = self.baseurl + "projectapps/copy.action"
- data = "appId=" + str(_id) + "&appName=" + applicationName + "-Copy"
- h = {"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"}
- response = self.http.request(url, method="POST", body=data, headers=h)
- self.userInfo(response[0]["status"], "Application", "copied")
-
- def viewApplicationOnline(self, _id):
- url = self.baseurl + "client/?id=" + str(_id)
- webbrowser.open(url)
-
- def createLayerTreeItem(self, data):
- url = self.baseurl + "rest/layertree"
- h = {"Content-type": "application/json"}
- body = json.dumps(data)
- response = self.http.request(url, method="POST", body=body, headers=h)
- return response[0]["status"]
-
- def updateLayerTreeItem(self, layerTreeItemId, data):
- url = self.baseurl + "rest/layertree/" + str(layerTreeItemId)
- h = {"Content-type": "application/json"}
- body = json.dumps(data)
- response = self.http.request(url, method="PUT", body=body, headers=h)
- return response[0]["status"]
-
- def deleteLayerTreeItem(self, layerTreeItemId):
- url = self.baseurl + "rest/layertree/" + str(layerTreeItemId)
- h = {"Content-type": "application/json"}
- body = json.dumps({"id": layerTreeItemId})
- response = self.http.request(url, method="DELETE", body=body, headers=h)
- return response[0]["status"]
diff --git a/docker-compose-dev.yml b/docker/docker-compose-dev.yml
similarity index 68%
rename from docker-compose-dev.yml
rename to docker/docker-compose-dev.yml
index 30984b4..8c87198 100644
--- a/docker-compose-dev.yml
+++ b/docker/docker-compose-dev.yml
@@ -10,4 +10,5 @@ services:
volumes:
- /tmp/.X11-unix:/tmp/.X11-unix
- ./qgis_plugins:/root/.local/share/QGIS/QGIS3/profiles/default/python/plugins
- - ../qgis-shogun-editor:/root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/qgis-shogun-editor
+ - ../plugin_code:/root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/qgis-shogun-editor
+ - ./shogun_qgis_konfigurator.qgs:/root/shogun_qgis_konfigurator.qgs
diff --git a/docker/shogun_qgis_konfigurator.qgs b/docker/shogun_qgis_konfigurator.qgs
new file mode 100644
index 0000000..678b1d8
--- /dev/null
+++ b/docker/shogun_qgis_konfigurator.qgs
@@ -0,0 +1,492 @@
+
+
+
+
+
+
+
+
+ PROJCRS["ETRS89 / UTM zone 32N",BASEGEOGCRS["ETRS89",ENSEMBLE["European Terrestrial Reference System 1989 ensemble",MEMBER["European Terrestrial Reference Frame 1989"],MEMBER["European Terrestrial Reference Frame 1990"],MEMBER["European Terrestrial Reference Frame 1991"],MEMBER["European Terrestrial Reference Frame 1992"],MEMBER["European Terrestrial Reference Frame 1993"],MEMBER["European Terrestrial Reference Frame 1994"],MEMBER["European Terrestrial Reference Frame 1996"],MEMBER["European Terrestrial Reference Frame 1997"],MEMBER["European Terrestrial Reference Frame 2000"],MEMBER["European Terrestrial Reference Frame 2005"],MEMBER["European Terrestrial Reference Frame 2014"],ELLIPSOID["GRS 1980",6378137,298.257222101,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[0.1]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4258]],CONVERSION["UTM zone 32N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Europe between 6°E and 12°E: Austria; Belgium; Denmark - onshore and offshore; Germany - onshore and offshore; Norway including - onshore and offshore; Spain - offshore."],BBOX[38.76,6,84.33,12]],ID["EPSG",25832]]
+ +proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs
+ 2105
+ 25832
+ EPSG:25832
+ ETRS89 / UTM zone 32N
+ utm
+ EPSG:7019
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Mapnik_OSM_rendering_optimised_for_WMS_use_0d1ff518_14de_450d_b87d_4df2c98bccfc
+
+
+
+
+
+
+
+
+ meters
+
+ 489339.30737446394050494
+ 5662221.97572672180831432
+ 490170.07104633894050494
+ 5663478.88978922180831432
+
+ 0
+
+
+ PROJCRS["ETRS89 / UTM zone 32N",BASEGEOGCRS["ETRS89",ENSEMBLE["European Terrestrial Reference System 1989 ensemble",MEMBER["European Terrestrial Reference Frame 1989"],MEMBER["European Terrestrial Reference Frame 1990"],MEMBER["European Terrestrial Reference Frame 1991"],MEMBER["European Terrestrial Reference Frame 1992"],MEMBER["European Terrestrial Reference Frame 1993"],MEMBER["European Terrestrial Reference Frame 1994"],MEMBER["European Terrestrial Reference Frame 1996"],MEMBER["European Terrestrial Reference Frame 1997"],MEMBER["European Terrestrial Reference Frame 2000"],MEMBER["European Terrestrial Reference Frame 2005"],MEMBER["European Terrestrial Reference Frame 2014"],ELLIPSOID["GRS 1980",6378137,298.257222101,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[0.1]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4258]],CONVERSION["UTM zone 32N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Europe between 6°E and 12°E: Austria; Belgium; Denmark - onshore and offshore; Germany - onshore and offshore; Norway including - onshore and offshore; Spain - offshore."],BBOX[38.76,6,84.33,12]],ID["EPSG",25832]]
+ +proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs
+ 2105
+ 25832
+ EPSG:25832
+ ETRS89 / UTM zone 32N
+ utm
+ EPSG:7019
+ false
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+ Beschriftungen_6d2fd0af_6451_4200_8a98_5f51362c1394
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+ true
+
+
+
+
+
+
+ 1
+ 1
+ 1
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+
+
+ -15855090.75202531367540359
+ -19091441.09884598851203918
+ 16855084.84629673138260841
+ 19995929.88587754219770432
+
+
+ -179.1490886292228879
+ -84.8708345117852474
+ 178.97612986387204614
+ 84.06872689490818118
+
+ Mapnik_OSM_rendering_optimised_for_WMS_use_0d1ff518_14de_450d_b87d_4df2c98bccfc
+ contextualWMSLegend=0&crs=EPSG:25832&dpiMode=7&featureCount=10&format=image/png&layers=mapnik&styles&url=http://full.wms.geofabrik.de/std/3978cb73ddab73fb9e9cee3ac3feae4b
+
+
+
+ Mapnik OSM rendering optimised for WMS use
+
+
+ PROJCRS["ETRS89 / UTM zone 32N",BASEGEOGCRS["ETRS89",ENSEMBLE["European Terrestrial Reference System 1989 ensemble",MEMBER["European Terrestrial Reference Frame 1989"],MEMBER["European Terrestrial Reference Frame 1990"],MEMBER["European Terrestrial Reference Frame 1991"],MEMBER["European Terrestrial Reference Frame 1992"],MEMBER["European Terrestrial Reference Frame 1993"],MEMBER["European Terrestrial Reference Frame 1994"],MEMBER["European Terrestrial Reference Frame 1996"],MEMBER["European Terrestrial Reference Frame 1997"],MEMBER["European Terrestrial Reference Frame 2000"],MEMBER["European Terrestrial Reference Frame 2005"],MEMBER["European Terrestrial Reference Frame 2014"],ELLIPSOID["GRS 1980",6378137,298.257222101,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[0.1]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4258]],CONVERSION["UTM zone 32N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Europe between 6°E and 12°E: Austria; Belgium; Denmark - onshore and offshore; Germany - onshore and offshore; Norway including - onshore and offshore; Spain - offshore."],BBOX[38.76,6,84.33,12]],ID["EPSG",25832]]
+ +proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs
+ 2105
+ 25832
+ EPSG:25832
+ ETRS89 / UTM zone 32N
+ utm
+ EPSG:7019
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+ true
+
+
+
+
+ wms
+
+
+
+
+
+
+
+
+ 1
+ 1
+ 1
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ None
+ WholeRaster
+ Estimated
+ 0.02
+ 0.98
+ 2
+
+
+
+
+
+ resamplingFilter
+
+ 0
+
+
+
+
+
+
+
+ 2
+
+
+ 255
+ 255
+ 255
+ 255
+ 0
+ 255
+ 255
+
+
+ false
+
+
+ WGS84
+
+
+ m2
+ meters
+
+
+ 50
+ 5
+ 16
+ 30
+ 2.5
+ false
+ true
+ false
+ false
+ 0
+ 0
+ false
+ false
+ true
+ 0
+ 255,0,0,255
+
+
+ false
+
+
+ true
+ 2
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ulrich Rothstein
+ 2020-03-09T14:41:36
+
+
+
+
+
+
+
+
+
+
+ PROJCRS["ETRS89 / UTM zone 32N",BASEGEOGCRS["ETRS89",ENSEMBLE["European Terrestrial Reference System 1989 ensemble",MEMBER["European Terrestrial Reference Frame 1989"],MEMBER["European Terrestrial Reference Frame 1990"],MEMBER["European Terrestrial Reference Frame 1991"],MEMBER["European Terrestrial Reference Frame 1992"],MEMBER["European Terrestrial Reference Frame 1993"],MEMBER["European Terrestrial Reference Frame 1994"],MEMBER["European Terrestrial Reference Frame 1996"],MEMBER["European Terrestrial Reference Frame 1997"],MEMBER["European Terrestrial Reference Frame 2000"],MEMBER["European Terrestrial Reference Frame 2005"],MEMBER["European Terrestrial Reference Frame 2014"],ELLIPSOID["GRS 1980",6378137,298.257222101,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[0.1]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4258]],CONVERSION["UTM zone 32N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Europe between 6°E and 12°E: Austria; Belgium; Denmark - onshore and offshore; Germany - onshore and offshore; Norway including - onshore and offshore; Spain - offshore."],BBOX[38.76,6,84.33,12]],ID["EPSG",25832]]
+ +proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs
+ 2105
+ 25832
+ EPSG:25832
+ ETRS89 / UTM zone 32N
+ utm
+ EPSG:7019
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]]
+ +proj=longlat +datum=WGS84 +no_defs
+ 3452
+ 4326
+ EPSG:4326
+ WGS 84
+ longlat
+ EPSG:7030
+ true
+
+
+
+
+
+
+
diff --git a/gui/dialog_bases/addraster.py b/gui/dialog_bases/addraster.py
deleted file mode 100644
index d488b6e..0000000
--- a/gui/dialog_bases/addraster.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-(c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/
- This code is licensed under the GPL 2.0 license.
-"""
-import sys
-
-if sys.version_info[0] >= 3:
- from qgis.PyQt import QtCore
- from qgis.PyQt.QtWidgets import QDialog, QLabel, QPushButton
-else:
- from PyQt4 import QtCore
- from PyQt4.QtGui import QDialog, QLabel, QPushButton
-
-__author__ = "ntreff"
-__date__ = "July 2025"
-
-
-class AddRasterDialog(QDialog):
- def __init__(self):
- super(QDialog, self).__init__()
- self.resize(410, 200)
- self.setWindowTitle("Add Raster Layer Dialog")
- self.label = QLabel(self)
- self.label.setGeometry(QtCore.QRect(20, 20, 300, 100))
- self.label.setAlignment(QtCore.Qt.AlignCenter)
- self.label.setText(
- "The requested ressource is a raster layer. \n"
- "Depending on it's size', downloading \nand importing it to QGIS"
- "may \ntake while. You can also consider to only import the\n"
- "layer as a WMS if you only need to view it"
- )
- self.cancelbutton = QPushButton(self)
- self.cancelbutton.setText("Cancel")
- self.cancelbutton.setGeometry(QtCore.QRect(20, 150, 125, 30))
- self.wmsbutton = QPushButton(self)
- self.wmsbutton.setText("Add as WMS Layer")
- self.wmsbutton.setGeometry(QtCore.QRect(150, 150, 125, 30))
- self.rasterbutton = QPushButton(self)
- self.rasterbutton.setText("Add as Raster Layer")
- self.rasterbutton.setGeometry(QtCore.QRect(280, 150, 125, 30))
-
- # we reconfigure the already existing slot self.done() (inherited
- # from QDialog to emit different ints - self.done() is called by
- # exec_())
- self.cancelbutton.clicked.connect(lambda: self.done(0))
- self.wmsbutton.clicked.connect(lambda: self.done(1))
- self.rasterbutton.clicked.connect(lambda: self.done(2))
diff --git a/gui/dialog_bases/applicationSettings.py b/gui/dialog_bases/applicationSettings.py
deleted file mode 100644
index f8872ae..0000000
--- a/gui/dialog_bases/applicationSettings.py
+++ /dev/null
@@ -1,634 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-(c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/
- This code is licensed under the GPL 2.0 license.
-"""
-
-# we are faking the old way of QtGui, not the best style, but makes it easier
-# for switching betweeng version 2 and 3
-from qgis.PyQt import QtWidgets as QtGui
-from qgis.PyQt.QtCore import QRect, Qt
-from qgis.PyQt.QtGui import QFont
-
-__author__ = "ntreff"
-__date__ = "July 2025"
-
-
-class LayerListItem(QtGui.QListWidgetItem):
- def __init__(self, text, layerId):
- super(LayerListItem, self).__init__(text)
- self.setFlags(Qt.ItemIsEnabled | Qt.ItemIsDragEnabled | Qt.ItemIsSelectable)
- self.layerId = layerId
-
-
-class LayerListWidget(QtGui.QListWidget):
- def __init__(self, parent):
- super(LayerListWidget, self).__init__(parent)
- self.setDragEnabled(True)
- self.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
-
- def populateList(self, layers):
- for layer in layers:
- item = LayerListItem(text=layer[1], layerId=layer[0])
- self.addItem(item)
-
- def dragEnterEvent(self, e):
- item = self.itemAt(e.pos())
- if item is not None:
- # we need to pass the name of the layer and it's id to the mimeData
- # for drag and drop. For reasons of simplicity we just add the
- # layerId and name to one string which we pass and decode it later
- # we use '&;*&' so this code must not appear in layer names
- mimeText = item.text() + "&;*&" + str(item.layerId)
- e.mimeData().setText(mimeText)
-
-
-class LayerTreeItem(QtGui.QTreeWidgetItem):
- def __init__(self, parent):
- super(LayerTreeItem, self).__init__(parent)
- self.setFlags(
- Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled |
- Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsDropEnabled
- )
- self.savedAttributes = {}
- self.newAttributes = {}
- self.layerId = None
- self.id = None
-
- def setSavedAttributes(self, savedAttributes):
- self.savedAttributes = savedAttributes
- self.setText(0, self.savedAttributes["text"])
- self.role = self.savedAttributes["@class"]
- self.id = self.savedAttributes["id"]
- if self.savedAttributes["root"]:
- return
- if self.savedAttributes["checked"]:
- self.setCheckState(0, Qt.Checked)
- else:
- self.setCheckState(0, Qt.Unchecked)
-
- def updateNewAttributes(self):
- self.newAttributes["text"] = self.text(0)
- self.newAttributes["root"] = False
- self.newAttributes["@class"] = self.role
-
- if self.checkState(0) == Qt.Checked:
- self.newAttributes["checked"] = True
- else:
- self.newAttributes["checked"] = False
-
- if self.parent() is None:
- self.newAttributes["parentId"] = self.treeWidget().rootId
- self.newAttributes["index"] = self.treeWidget().indexOfTopLevelItem(self)
- else:
- self.newAttributes["parentId"] = self.parent().id
- self.newAttributes["index"] = self.parent().indexOfChild(self)
-
- if self.role == "de.terrestris.appshogun.model.tree.LayerTreeLeaf":
- self.newAttributes["expandable"] = False
- self.newAttributes["expanded"] = False
- self.newAttributes["leaf"] = True
- else:
- self.newAttributes["expandable"] = True
- self.newAttributes["expanded"] = True
- self.newAttributes["leaf"] = False
-
- if self.layerId is not None:
- self.newAttributes["layer"] = self.layerId
- # self.newAttributes['expanded'] = self.isExpanded()
-
- def getItemChange(self):
- if len(self.savedAttributes) == 0:
- return self.newAttributes
- else:
- change = {}
- for key, value in self.savedAttributes.items():
- if key in self.newAttributes:
- if value != self.newAttributes[key]:
- change[key] = self.newAttributes[key]
- if len(change) == 0:
- return None
- else:
- change["id"] = self.id
- return change
-
-
-class LayerTreeWidget(QtGui.QTreeWidget):
- SHOGUN_TREE_LEAF = "de.terrestris.appshogun.model.tree.LayerTreeLeaf"
- SHOGUN_TREE_FOLDER = "de.terrestris.appshogun.model.tree.LayerTreeFolder"
-
- def __init__(self, parentWindow):
- super(LayerTreeWidget, self).__init__(parentWindow)
- self.setHeaderHidden(True)
- self.setColumnCount(1)
- self.setDragEnabled(True)
- self.setAcceptDrops(True)
- self.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
- self.setContextMenuPolicy(Qt.CustomContextMenu)
- self.customContextMenuRequested.connect(self.on_context_menu)
- self.deletedItemIds = []
-
- def setupNewTree(self):
- folder = self.addNewFolder(None)
- folder.setText(0, "Background layer")
-
- def populateTree(self, layerTree):
- # delete all items, then populate the tree with items representing
- # the layer tree structure
- self.clear()
- self.rootId = layerTree["id"]
- self.constructTreeChildrenRecursive(
- self.invisibleRootItem(), layerTree["children"]
- )
-
- iterator = QtGui.QTreeWidgetItemIterator(self)
- val = iterator.value()
- while val:
- val.setExpanded(True)
- iterator += 1
- val = iterator.value()
-
- def constructTreeChildrenRecursive(self, parent, children):
- for child in children:
- item = LayerTreeItem(parent)
- attrs = {key: value for (key, value) in child.items() if key != "children"}
- item.setSavedAttributes(attrs)
- if "children" in child.keys():
- if not child["children"]:
- return
- # 'children' is a list, to make sure the items are put into the
- # tree in the right order according to there index, we sort them
- sortedChildren = sorted(child["children"], key=lambda x: x["index"])
- self.constructTreeChildrenRecursive(item, sortedChildren)
-
- def getLayerTreeChanges(self):
- allChanges = {"newItems": [], "changeItems": [], "deleteItems": []}
- # iterate through all items in the layertree and find new or
- # changed items
- iterator = QtGui.QTreeWidgetItemIterator(self)
- treeitem = iterator.value()
- while treeitem:
- treeitem.updateNewAttributes()
- change = treeitem.getItemChange()
- if change is not None:
- if "id" not in change:
- allChanges["newItems"].append(change)
- else:
- allChanges["changeItems"].append(change)
-
- iterator += 1
- treeitem = iterator.value()
-
- if len(self.deletedItemIds) > 0:
- allChanges["deleteItems"] = list(self.deletedItemIds)
- self.deletedItemIds = []
-
- for x in allChanges:
- if len(allChanges[x]) > 0:
- return allChanges
- return None
-
- def dropEvent(self, e):
- dropItem = self.itemAt(e.pos())
- mime = e.mimeData()
- if dropItem is None:
- if mime.hasText():
- newItem = LayerTreeItem(parent=self)
- layerName, layerId = mime.text().split("&;*&")
- newItem.setText(0, layerName)
- newItem.role = self.SHOGUN_TREE_LEAF
- newItem.layerId = int(layerId)
- newItem.setCheckState(0, Qt.Checked)
- else:
- self.changePositionInTree(self.invisibleRootItem())
- else:
- # if dropItem is a TreeLeaf it represents a layer so it cannot get a
- # child, so insert the dragged item in the parent folder
- if dropItem.role == self.SHOGUN_TREE_LEAF:
- if dropItem.parent() is not None:
- dropItem = dropItem.parent()
- else:
- dropItem = self.invisibleRootItem()
-
- # if mime has Text its coming from the layerlistwidget
- if mime.hasText():
- newItem = LayerTreeItem(parent=dropItem)
- layerName, layerId = mime.text().split("&;*&")
- newItem.setText(0, layerName)
- newItem.role = self.SHOGUN_TREE_LEAF
- newItem.layerId = int(layerId)
- newItem.setCheckState(0, Qt.Checked)
- else:
- self.changePositionInTree(dropItem)
- iterator = QtGui.QTreeWidgetItemIterator(self)
- val = iterator.value()
- while val:
- val.setSelected(False)
- iterator += 1
- val = iterator.value()
-
- def changePositionInTree(self, newParentItem):
- selectedItem = self.selectedItems()[0]
- oldParentItem = selectedItem.parent()
- # cannot insert a folder to itself
- cursor = newParentItem
- while cursor is not None:
- if selectedItem == cursor:
- return
- cursor = cursor.parent()
-
- if oldParentItem is None:
- self.invisibleRootItem().removeChild(selectedItem)
- else:
- oldParentItem.removeChild(selectedItem)
- newParentItem.addChild(selectedItem)
- newParentItem.setExpanded(True)
- selectedItem.setExpanded(True)
-
- def on_context_menu(self, point):
- item = self.itemAt(point)
- acts = []
- if item is None:
- a1 = QtGui.QAction("Add Folder (top level)", None)
- a1.triggered.connect(lambda: self.addNewFolder(None))
- acts.append(a1)
- a2 = QtGui.QAction("Delete Tree Contents completely", None)
- a2.triggered.connect(self.deleteAll)
- acts.append(a2)
- else:
- a1 = QtGui.QAction("Rename", None)
- a1.triggered.connect(lambda: self.renameItem(item))
- acts.append(a1)
- if item.role == self.SHOGUN_TREE_LEAF:
- a2 = QtGui.QAction("Delete Leaf", None)
- a2.triggered.connect(lambda: self.deleteLeaf(item))
- acts.append(a2)
- else:
- a2 = QtGui.QAction("New Folder (inside selected)", None)
- a2.triggered.connect(lambda: self.addNewFolder(item))
- acts.append(a2)
- a3 = QtGui.QAction("Delete Folder", None)
- a3.triggered.connect(lambda: self.deleteLeaf(item))
- acts.append(a3)
- menu = QtGui.QMenu()
- menu.addActions(acts)
- point = self.mapToGlobal(point)
- menu.exec_(point)
-
- def addNewFolder(self, item):
- if item is None:
- parent = self
- else:
- parent = item
- new = LayerTreeItem(parent)
- new.setText(0, "New folder")
- new.role = self.SHOGUN_TREE_FOLDER
- new.setCheckState(0, Qt.Checked)
- return new
-
- def deleteAll(self):
- topitem = self.invisibleRootItem()
- for index in range(topitem.childCount()):
- child = topitem.child(index)
- topitem.removeChild(child)
-
- def renameItem(self, item):
- text, ok = QtGui.QInputDialog.getText(
- self, "Text Input Dialog", "Enter the new name:"
- )
- if ok:
- item.setText(0, text)
-
- def getSubtreeIds(self, item):
- # as there is no option in QT to iterate only a subtree, we had to write
- # a recursive iteration by ourselves to get all id's that are about to
- # be deleted
- idList = []
- if item.id is not None:
- idList.append(item.id)
- if item.childCount() > 0:
- for x in range(item.childCount()):
- idList.extend(self.getSubtreeIds(item.child(x)))
- return idList
-
- def deleteLeaf(self, item):
- self.deletedItemIds.extend(self.getSubtreeIds(item))
-
- parent = item.parent()
- if parent is None:
- parent = self.invisibleRootItem()
- parent.removeChild(item)
-
-
-class ApplicationSettingsDialog(QtGui.QDialog):
- def __init__(self):
- QtGui.QDialog.__init__(self)
- self.tabs = [] # All child-tabWidgets
- self.tabedits = [] # All QLineEdits per tabWidget in a list
- self.tabboxes = [] # All QCheckBoxes per tabWidget in a list
- self.moreObjects = []
- self.setupUi()
-
- def setupUi(self):
- self.resize(550, 550)
- self.setWindowTitle("Settings")
-
- # create tabWidget that holds the tabs
- self.tabWidget = QtGui.QTabWidget(self)
- self.tabWidget.setGeometry(QRect(10, 20, 500, 480))
- self.tabWidget.setObjectName("tabWidget")
- tab0labels = [
- ["Name", (50, 50, 56, 17)],
- ["Description", (50, 100, 70, 25)],
- ["Language", (50, 150, 56, 17)],
- ]
- tab1labels = [
- [
- "Which tools/ buttons shall be activated in the application:",
- (50, 25, 56, 17),
- ]
- ]
- tab2labels = [
- ["Center:", (50, 50, 70, 17)],
- ["X:", (160, 53, 10, 10)],
- ["Y:", (320, 53, 10, 10)],
- ["Zoom:", (50, 100, 70, 17)],
- ["Extent:", (50, 363, 70, 17)],
- ["MinX:", (132, 367, 40, 17)],
- ["MinY:", (222, 327, 40, 17)],
- ["MaxX:", (306, 367, 40, 17)],
- ["MaxY:", (222, 407, 40, 17)],
- ]
- tab3labels = [
- ["All Layers", (90, 40, 80, 30)],
- ["Layer Tree", (325, 40, 80, 30)],
- ]
- tab4labels = [["Users", (100, 10, 50, 20)], ["Groups", (320, 10, 50, 20)]]
- tabwidgets = [
- ["General", tab0labels],
- ["Tools", tab1labels],
- ["Homeview", tab2labels],
- ["Layer", tab3labels],
- ["Permissions", tab4labels],
- ]
-
- # first set the labes for all tabwwidgets in a loop:
- for tab in tabwidgets:
- t = QtGui.QWidget()
- t.setObjectName(tab[0])
- self.tabs.append(t)
- self.tabWidget.addTab(t, tab[0])
-
- for label in tab[1]:
- l2 = QtGui.QLabel(t)
- l2.setText(label[0])
- l2.setGeometry(QRect(label[1][0], label[1][1], label[1][2], label[1][3]))
- if tab[0] == "Layer":
- font = QFont("Arial", 12)
- font.setBold(True)
- l2.setFont(font)
-
- self.tabWidget.setCurrentIndex(0)
-
- # then populate the specific tabwidgets with other QObjects:
- # tab 0 = 'General':
- self.nameEdit = QtGui.QLineEdit(self.tabs[0])
- self.nameEdit.setGeometry(QRect(250, 40, 150, 27))
- self.tabedits.append(self.nameEdit)
-
- self.descriptionEdit = QtGui.QLineEdit(self.tabs[0])
- self.descriptionEdit.setGeometry(QRect(250, 90, 150, 27))
- self.tabedits.append(self.descriptionEdit)
-
- self.languageBox = QtGui.QComboBox(self.tabs[0])
- self.languageBox.setGeometry(QRect(250, 140, 113, 27))
- self.languageBox.addItems(["en", "de"])
- self.tabedits.append(self.languageBox)
-
- self.boxPublic = QtGui.QCheckBox(self.tabs[0])
- self.boxPublic.setGeometry(QRect(250, 180, 80, 17))
- self.boxPublic.setText("Public")
- self.tabboxes.append(self.boxPublic)
-
- self.boxActive = QtGui.QCheckBox(self.tabs[0])
- self.boxActive.setGeometry(QRect(250, 230, 80, 17))
- self.boxActive.setText("Active")
- self.tabboxes.append(self.boxActive)
-
- # tab 1 = 'Tools':
- toollist = [
- "Zoom in button",
- "Zoom out button",
- "Zoom to extent button",
- "Step back to previous extent button",
- "Step forward to next extent button",
- "Activate hover-select tool",
- "Print button",
- "Show measure tools button",
- "Show redlining tools button",
- "Show workstate tools button",
- "Show addwms tools button",
- "Show meta toolbar button",
- ]
- y = 50
- self.tools = {}
- # a dictonary with toolbutton id as key and reference to the QCheckBox
- # as value, i.e.: {58: -Reference to QCheckBox Object-}
- tcount = 57
- for tool in toollist:
- t = QtGui.QCheckBox(self.tabs[1])
- t.setGeometry(QRect(60, y, 180, 17))
- t.setText(tool)
- self.tools[tcount] = t
- y += 30
- tcount += 1
-
- # tab 2 = 'Homeview':
- self.homeviewCenterEditX = QtGui.QLineEdit(self.tabs[2])
- self.homeviewCenterEditX.setGeometry(QRect(170, 50, 125, 25))
- self.tabedits.append(self.homeviewCenterEditX)
- self.homeviewCenterEditY = QtGui.QLineEdit(self.tabs[2])
- self.homeviewCenterEditY.setGeometry(QRect(330, 50, 125, 25))
- self.tabedits.append(self.homeviewCenterEditY)
-
- self.homeviewZoomBox = QtGui.QSpinBox(self.tabs[2])
- self.homeviewZoomBox.setGeometry(QRect(170, 100, 40, 25))
- self.moreObjects.append(self.homeviewZoomBox)
-
- self.extentEdits = []
- minX = QtGui.QLineEdit(self.tabs[2])
- minX.setGeometry(175, 360, 120, 25)
- self.extentEdits.append(minX)
-
- minY = QtGui.QLineEdit(self.tabs[2])
- minY.setGeometry(265, 320, 120, 25)
- self.extentEdits.append(minY)
-
- maxX = QtGui.QLineEdit(self.tabs[2])
- maxX.setGeometry(350, 360, 120, 25)
- self.extentEdits.append(maxX)
-
- maxY = QtGui.QLineEdit(self.tabs[2])
- maxY.setGeometry(265, 400, 120, 25)
- self.extentEdits.append(maxY)
-
- style = "QLineEdit { background-color : #a6a6a6; color : white; }"
- for edit in self.extentEdits:
- edit.setReadOnly(True)
- edit.lower()
- edit.setStyleSheet(style)
-
- self.origExtentButton = QtGui.QPushButton(self.tabs[2])
- self.origExtentButton.setGeometry(100, 150, 190, 30)
- self.origExtentButton.setText("Set original homview")
- self.moreObjects.append(self.origExtentButton)
-
- self.qgsExtentButton = QtGui.QPushButton(self.tabs[2])
- self.qgsExtentButton.setGeometry(290, 150, 190, 30)
- self.qgsExtentButton.setText("Set current QGIS view")
- self.moreObjects.append(self.qgsExtentButton)
-
- self.homeviewEpsgWarning = QtGui.QLabel(self.tabs[2])
- self.homeviewEpsgWarning.setGeometry(QRect(50, 220, 435, 80))
- self.homeviewEpsgWarning.setFont(QFont("Arial", 9))
-
- self.jumpButtonOrig = QtGui.QPushButton(self.tabs[2])
- self.jumpButtonOrig.setGeometry(QRect(115, 192, 160, 20))
- self.jumpButtonOrig.setText("Jump to original homeview")
- self.jumpButtonOrig.setStyleSheet(
- "QPushButton { background-color : #a6a6a6; color : white; }"
- )
-
- self.jumpButtonNew = QtGui.QPushButton(self.tabs[2])
- self.jumpButtonNew.setGeometry(QRect(305, 192, 160, 20))
- self.jumpButtonNew.setText("Jump to new homeview")
- self.moreObjects.append(self.jumpButtonNew)
-
- # tab 3 = 'Layer' (layertree)
-
- self.layerlistwidget = LayerListWidget(self.tabs[3])
- self.layerlistwidget.setGeometry(QRect(25, 70, 210, 350))
-
- self.layertreewidget = LayerTreeWidget(self.tabs[3])
- self.layertreewidget.setGeometry(QRect(260, 70, 210, 350))
-
- # tab 4 = 'Permissions'
- self.usertabel = QtGui.QTableWidget(self.tabs[4])
- self.usertabel.setGeometry(QRect(10, 30, 230, 300))
- self.usertabel.setColumnCount(3)
- self.usertabel.setHorizontalHeaderLabels(["Read", "Update", "Delete"])
- self.moreObjects.append(self.usertabel)
-
- self.groupstabel = QtGui.QTableWidget(self.tabs[4])
- self.groupstabel.setGeometry(QRect(250, 30, 230, 300))
- self.groupstabel.setColumnCount(3)
- self.groupstabel.setHorizontalHeaderLabels(["Read", "Update", "Delete"])
- self.moreObjects.append(self.groupstabel)
-
- # create Gui surrounding the tabs
- self.editCheckBox = QtGui.QCheckBox(self)
- self.editCheckBox.setGeometry(QRect(420, 10, 50, 17))
- self.editCheckBox.setText("Edit")
-
- self.pushButtonOk = QtGui.QPushButton(self)
- self.pushButtonOk.setGeometry(QRect(420, 500, 85, 27))
- self.pushButtonCancel = QtGui.QPushButton(self)
- self.pushButtonCancel.setGeometry(QRect(320, 500, 85, 27))
- self.pushButtonCancel.setText("Cancel")
-
- self.warnLabel = QtGui.QLabel(self)
- self.warnLabel.setGeometry(QRect(300, 505, 80, 15))
- self.warnLabel.setText("Please fill out all mandatory fields")
- self.warnLabel.setHidden(True)
- self.warnLabel.setStyleSheet("QLabel { color : #ff6666; }")
-
- def setEditState(self, b): # b = true or false
- if b:
- self.pushButtonOk.setText("Save Changes")
- self.pushButtonCancel.setHidden(False)
-
- for editable in self.getAllEditables():
- editable.setEnabled(b)
-
- else:
- self.pushButtonCancel.setHidden(True)
- self.pushButtonOk.setText("OK")
-
- for editable in self.getAllEditables():
- editable.setEnabled(b)
-
- def getAllEditables(self):
- editable_list = []
- for edit in self.tabedits:
- editable_list.append(edit)
- for box in self.tabboxes:
- editable_list.append(box)
- for obj in self.moreObjects:
- editable_list.append(obj)
- for box in self.tools.values():
- editable_list.append(box)
- editable_list.append(self.layerlistwidget)
- editable_list.append(self.layertreewidget)
- return editable_list
-
- def populateTable(self, table, usersList):
- if table == "users":
- table = self.usertabel
- else:
- table = self.groupstabel
-
- tableRowCount = len(usersList)
- table.setRowCount(tableRowCount)
-
- for row in range(tableRowCount):
- user = usersList[row]
- table.setVerticalHeaderItem(
- row, QtGui.QTableWidgetItem(user["displayTitle"])
- )
- permissions = user["permissions"]
- permList = ["Read", "Update", "Delete"]
- if not permissions:
- for x in permList:
- item = QtGui.QTableWidgetItem(x)
- item.setCheckState(Qt.Unchecked)
- table.setItem(row, permList.index(x), item)
- else:
- for x in permList:
- item = QtGui.QTableWidgetItem(x)
- if x.upper() in permissions["permissions"]:
- item.setCheckState(Qt.Checked)
- else:
- item.setCheckState(Qt.Unchecked)
- table.setItem(row, permList.index(x), item)
-
- def noPermissionAccess(self):
- self.usertabel.setHidden(True)
- self.groupstabel.setHidden(True)
- self.noPermissionAccessLabel = QtGui.QLabel(self.tabs[4])
- self.noPermissionAccessLabel.setGeometry(QRect(50, 50, 420, 50))
- self.noPermissionAccessLabel.setText(
- "Could not access application "
- "permissions. User permission is not high enough"
- )
-
- def newAppCreation(self):
- self.usertabel.setHidden(True)
- self.groupstabel.setHidden(True)
- self.noPermissionAccessLabel = QtGui.QLabel(self.tabs[4])
- self.noPermissionAccessLabel.setGeometry(QRect(50, 50, 420, 100))
- self.noPermissionAccessLabel.setText(
- "You have to save and upload the "
- "new application once\nfirst, then you can edit it's permissions"
- )
-
- def showEpsgWarning(self, currentCrs, applicationCrs):
- self.homeviewEpsgWarning.setHidden(False)
- txt = "Note: The coordinate reference system CRS of the current QGIS \n"
- txt += "project: " + currentCrs + " is different from this application's"
- txt += " CRS: " + applicationCrs + "\nIt is strongly recommended to "
- txt += "change the QGIS Project CRS\nto " + applicationCrs + " before "
- txt += "working with the homeview"
- self.homeviewEpsgWarning.setText(txt)
-
- def hideEpsgWarning(self):
- self.homeviewEpsgWarning.setHidden(True)
diff --git a/gui/dialog_bases/connectdlg.py b/gui/dialog_bases/connectdlg.py
deleted file mode 100644
index 55cf0db..0000000
--- a/gui/dialog_bases/connectdlg.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-(c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/
- This code is licensed under the GPL 2.0 license.
-"""
-
-import sys
-
-if sys.version_info[0] >= 3:
- from qgis.PyQt.QtCore import QRect
- from qgis.PyQt.QtWidgets import QDialog, QLabel, QLineEdit, QPushButton
-else:
- from PyQt4.QtCore import QRect
- from PyQt4.QtGui import QDialog, QLabel, QLineEdit, QPushButton
-
-__author__ = "ntreff"
-__date__ = "July 2025"
-
-
-class ConnectDialog(QDialog):
- def __init__(self):
- QDialog.__init__(self)
- self.resize(400, 300)
- self.setWindowTitle("Connection Dialog")
-
- self.label = QLabel(self)
- self.label.setGeometry(QRect(140, 150, 81, 20))
- self.label.setText("username")
- self.label2 = QLabel(self)
- self.label2.setGeometry(QRect(140, 190, 81, 20))
- self.label2.setText("password")
- self.label3 = QLabel(self)
- self.label3.setGeometry(QRect(40, 32, 151, 20))
- self.label3.setText("Name of the Shogun Client")
- self.label4 = QLabel(self)
- self.label4.setGeometry(QRect(40, 82, 151, 20))
- self.label4.setText("URL:")
-
- self.nameIn = QLineEdit(self)
- self.nameIn.setGeometry(QRect(200, 30, 180, 27))
- self.nameIn.setText("Default Shogun Client")
- self.urlIn = QLineEdit(self)
- self.urlIn.setGeometry(QRect(122, 80, 258, 27))
- self.urlIn.setPlaceholderText("i. e.: http(s)://.../shogun2-webapp")
- self.userIn = QLineEdit(self)
- self.userIn.setGeometry(QRect(230, 150, 150, 27))
- self.passwordIn = QLineEdit(self)
- self.passwordIn.setGeometry(QRect(230, 190, 150, 27))
- self.passwordIn.setEchoMode(QLineEdit.Password)
- self.okButton = QPushButton(self)
- self.okButton.setGeometry(QRect(270, 240, 85, 27))
- self.okButton.setText("OK")
- self.cancelButton = QPushButton(self)
- self.cancelButton.setGeometry(QRect(180, 240, 85, 27))
- self.cancelButton.setText("Cancel")
diff --git a/gui/dialog_bases/dockwidget.py b/gui/dialog_bases/dockwidget.py
deleted file mode 100644
index 84b0334..0000000
--- a/gui/dialog_bases/dockwidget.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-(c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/
- This code is licensed under the GPL 2.0 license.
-"""
-
-import sys
-
-if sys.version_info[0] >= 3:
- from qgis.PyQt.QtCore import QRect, Qt
- from qgis.PyQt.QtWidgets import QDockWidget, QPushButton, QTreeWidget, QWidget
-else:
- from PyQt4.QtCore import QRect, Qt
- from PyQt4.QtGui import QDockWidget, QPushButton, QTreeWidget, QWidget
-
-__author__ = "ntreff"
-__date__ = "July 2025"
-
-
-class DockWidget(QDockWidget):
- def __init__(self):
- QDockWidget.__init__(self)
- self.setWindowTitle("Shogun Editor")
- self.setContextMenuPolicy(Qt.DefaultContextMenu)
- self.setLayoutDirection(Qt.LeftToRight)
- self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
- self.setFloating(False)
-
- self.dockWidgetContents = QWidget(self)
- self.dockWidgetContents.setGeometry(QRect(20, 30, 320, 700))
- self.newConnectionButton = QPushButton(self.dockWidgetContents)
- self.newConnectionButton.setGeometry(QRect(10, 0, 141, 27))
- self.newConnectionButton.setText("New Connection")
- self.treeWidget = QTreeWidget(self.dockWidgetContents)
- self.treeWidget.setGeometry(QRect(10, 40, 300, 650))
- self.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
- self.treeWidget.setHeaderHidden(True)
- self.treeWidget.setColumnCount(1)
diff --git a/gui/dialog_bases/layerSettings.py b/gui/dialog_bases/layerSettings.py
deleted file mode 100644
index fcc9d91..0000000
--- a/gui/dialog_bases/layerSettings.py
+++ /dev/null
@@ -1,270 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-(c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/
- This code is licensed under the GPL 2.0 license.
-"""
-
-import sys
-
-from qgis.gui import QgsMapLayerComboBox
-
-if sys.version_info[0] >= 3:
- # we are faking the old way of QtGui, not the best style, but makes it easier
- # for switching betweeng version 2 and 3
- from qgis.PyQt import QtWidgets as QtGui
- from qgis.PyQt.QtCore import QRect, Qt
- from qgis.PyQt.QtGui import QDoubleValidator
-else:
- from PyQt4 import QtGui
- from PyQt4.QtCore import QRect, Qt
-
-__author__ = "ntreff"
-__date__ = "July 2025"
-
-
-class LayerSettingsDialog(QtGui.QDialog):
- def __init__(self):
- QtGui.QDialog.__init__(self)
- self.tabs = [] # All child-tabWidgets
- self.tabedits = [] # All QLineEdits and QComboBox per tabWidget in a list
- self.tabboxes = [] # All QCheckBoxes per tabWidget in a list
- self.moreObjects = []
- self.setupUi()
-
- def setupUi(self):
- self.resize(550, 550)
- self.setWindowTitle("Settings")
-
- # create tabWidget that holds the tabs
- self.tabWidget = QtGui.QTabWidget(self)
- self.tabWidget.setGeometry(QRect(10, 20, 500, 480))
- self.tabWidget.setObjectName("tabWidget")
- tab0labels = [
- ["Name", (50, 50, 56, 17)],
- ["Layer Opacity", (50, 100, 80, 25)],
- ["Hover Template", (50, 150, 120, 17)],
- ]
- tab1labels = [
- [
- 'Until now "Metadata" has to be edited in the shogun2-webapp',
- (50, 50, 300, 17),
- ]
- ]
- tab2labels = [["explanation", (50, 50, 400, 200)]]
- tab3labels = [["Users", (100, 10, 50, 20)], ["Groups", (320, 10, 50, 20)]]
- tabwidgets = [
- ["General", tab0labels],
- ["Metadata", tab1labels],
- ["Style", tab2labels],
- ["Permissions", tab3labels],
- ]
-
- expl = "To edit the style of layer in shogun, first add the layer to QGIS.\n"
- expl += "Then style the layer via the QGIS layer properties.\nWhen finished, "
- expl += "you can upload the current layer style \nto this layer in Shogun by "
- expl += "right-clicking it in \nthe Shogun Editor menu"
-
- # first set the labes for all tabwwidgets in a loop:
- for tab in tabwidgets:
- t = QtGui.QWidget()
- t.setObjectName(tab[0])
- self.tabs.append(t)
- self.tabWidget.addTab(t, tab[0])
-
- for label in tab[1]:
- l2 = QtGui.QLabel(t)
- l2.setGeometry(QRect(label[1][0], label[1][1], label[1][2], label[1][3]))
- if label[0] == "explanation":
- l2.setText(expl)
- l2.setAlignment(Qt.AlignTop)
- else:
- l2.setText(label[0])
-
- self.tabWidget.setCurrentIndex(0)
-
- # then populate the specific tabwidgets with other QObjects:
- # tab 0 = 'General':
- self.nameEdit = QtGui.QLineEdit(self.tabs[0])
- self.nameEdit.setGeometry(QRect(180, 40, 113, 27))
- self.tabedits.append(self.nameEdit)
-
- self.sliderEdit = QtGui.QLineEdit(self.tabs[0])
- self.sliderEdit.setGeometry(QRect(400, 90, 30, 23))
- self.sliderEdit.setInputMask("9.99")
- if sys.version_info[0] >= 3:
- validator = QDoubleValidator(-0.01, 1.01, 2)
- else:
- validator = QtGui.QDoubleValidator(-0.01, 1.01, 2)
- self.sliderEdit.setValidator(validator)
- self.tabedits.append(self.sliderEdit)
-
- self.hoverEdit = QtGui.QLineEdit(self.tabs[0])
- self.hoverEdit.setGeometry(QRect(180, 140, 113, 27))
- self.tabedits.append(self.hoverEdit)
-
- self.hoverBox = QtGui.QComboBox(self.tabs[0])
- self.hoverBox.setGeometry(QRect(320, 140, 80, 27))
- self.tabedits.append(self.hoverBox)
-
- self.hoverAddButton = QtGui.QPushButton(self.tabs[0])
- self.hoverAddButton.setGeometry(QRect(410, 140, 30, 27))
- self.hoverAddButton.setText("Add")
- self.tabedits.append(self.hoverAddButton)
-
- self.slider = QtGui.QSlider(self.tabs[0])
- self.slider.setGeometry(QRect(180, 90, 160, 18))
- self.slider.setOrientation(Qt.Horizontal)
- self.slider.setMaximum(100)
- self.slider.setMinimum(-1)
- self.slider.setEnabled(False)
- self.moreObjects.append(self.slider)
- self.slider.valueChanged.connect(
- lambda: self.sliderEdit.setText(str(float(self.slider.value()) / 100))
- )
- self.sliderEdit.textEdited.connect(
- lambda: self.slider.setValue(int(float(self.sliderEdit.text()) * 100))
- )
-
- self.hoverAddButton.clicked.connect(self.addHoverAttribute)
-
- # tab 3 = 'Permissions':
- self.usertabel = QtGui.QTableWidget(self.tabs[3])
- self.usertabel.setGeometry(QRect(10, 30, 230, 300))
- self.usertabel.setColumnCount(3)
- self.usertabel.setHorizontalHeaderLabels(["Read", "Update", "Delete"])
- self.moreObjects.append(self.usertabel)
-
- self.groupstabel = QtGui.QTableWidget(self.tabs[3])
- self.groupstabel.setGeometry(QRect(250, 30, 230, 300))
- self.groupstabel.setColumnCount(3)
- self.groupstabel.setHorizontalHeaderLabels(["Read", "Update", "Delete"])
- self.moreObjects.append(self.groupstabel)
-
- # create Gui surrounding the tabs
- self.editCheckBox = QtGui.QCheckBox(self)
- self.editCheckBox.setGeometry(QRect(420, 10, 50, 17))
- self.editCheckBox.setText("Edit")
-
- self.pushButtonOk = QtGui.QPushButton(self)
- self.pushButtonOk.setGeometry(QRect(420, 500, 85, 27))
- self.pushButtonCancel = QtGui.QPushButton(self)
- self.pushButtonCancel.setGeometry(QRect(320, 500, 85, 27))
- self.pushButtonCancel.setText("Cancel")
-
- def addHoverAttribute(self):
- attribute = self.hoverBox.currentText()
- if len(attribute) > 0:
- attribute = "{" + attribute + "}"
- text = self.hoverEdit.text() + attribute
- self.hoverEdit.setText(text)
-
- def setEditState(self, b): # b = true or false
- if b:
- self.pushButtonOk.setText("Save Changes")
- self.pushButtonCancel.setHidden(False)
- for editable in self.getAllEditables():
- editable.setEnabled(b)
- else:
- self.pushButtonCancel.setHidden(True)
- self.pushButtonOk.setText("OK")
- for editable in self.getAllEditables():
- editable.setEnabled(b)
-
- def getAllEditables(self):
- editable_list = []
- for edit in self.tabedits:
- editable_list.append(edit)
- for box in self.tabboxes:
- editable_list.append(box)
- for obj in self.moreObjects:
- editable_list.append(obj)
- return editable_list
-
- def deactivateHoverEditing(self):
- self.hoverBox.setHidden(True)
- self.hoverEdit.setHidden(True)
- self.hoverAddButton.setHidden(True)
- self.infoEdit = QtGui.QLineEdit(self.tabs[0])
- self.infoEdit.setEnabled(False)
- self.infoEdit.setText("only available for vector layers")
- self.infoEdit.setGeometry(QRect(180, 143, 200, 27))
-
- def populateTable(self, table, usersList):
- if table == "users":
- table = self.usertabel
- else:
- table = self.groupstabel
-
- usersList = sorted(usersList, key=lambda x: x["displayTitle"])
- tableRowCount = len(usersList)
- table.setRowCount(tableRowCount)
-
- for row in range(tableRowCount):
- user = usersList[row]
- table.setVerticalHeaderItem(
- row, QtGui.QTableWidgetItem(user["displayTitle"])
- )
- permissions = user["permissions"]
- permList = ["Read", "Update", "Delete"]
- if not permissions:
- for x in permList:
- item = QtGui.QTableWidgetItem(x)
- item.setCheckState(Qt.Unchecked)
- table.setItem(row, permList.index(x), item)
- else:
- perms = permissions["permissions"]
- for x in permList:
- item = QtGui.QTableWidgetItem(x)
- if perms[0] == "ADMIN" or x.upper() in perms:
- item.setCheckState(Qt.Checked)
- else:
- item.setCheckState(Qt.Unchecked)
- table.setItem(row, permList.index(x), item)
- table.sortItems(0, Qt.AscendingOrder)
-
- def noPermissionAccess(self):
- self.usertabel.setHidden(True)
- self.groupstabel.setHidden(True)
- self.noPermissionAccessLabel = QtGui.QLabel(self.tabs[3])
- self.noPermissionAccessLabel.setGeometry(QRect(50, 50, 420, 50))
- self.noPermissionAccessLabel.setText(
- "Could not access application "
- "permissions. User permission is not high enough"
- )
-
-
-class UploadLayerDialog(QtGui.QDialog):
- def __init__(self):
- QtGui.QDialog.__init__(self)
- self.setupUi()
-
- def setupUi(self):
- self.resize(400, 400)
- self.setWindowTitle("Upload layer to Shogun")
-
- title = QtGui.QLabel(self)
- title.setGeometry(50, 30, 300, 70)
- title.setText(
- "Please select the layer you wish to upload to the \n Shogun Server"
- )
-
- self.layerBox = QgsMapLayerComboBox(self)
- self.layerBox.setGeometry(QRect(50, 100, 300, 30))
-
- self.uploadButton = QtGui.QPushButton(self)
- self.uploadButton.setGeometry(QRect(250, 160, 100, 35))
- self.uploadButton.setText("Upload Layer")
-
- self.cancelButton = QtGui.QPushButton(self)
- self.cancelButton.setGeometry(QRect(140, 160, 100, 35))
- self.cancelButton.setText("Cancel")
- self.cancelButton.clicked.connect(self.hide)
-
- self.logWindow = QtGui.QTextEdit(self)
- self.logWindow.setGeometry(QRect(50, 200, 300, 180))
- self.logWindow.setReadOnly(True)
- self.logWindow.setText("Upload Log:")
-
- def log(self, message):
- msg = " - " + message
- self.logWindow.append(msg)
diff --git a/gui/editor.py b/gui/editor.py
deleted file mode 100644
index ad6b0e8..0000000
--- a/gui/editor.py
+++ /dev/null
@@ -1,256 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-(c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/
- This code is licensed under the GPL 2.0 license.
-"""
-
-from qgis.core import QgsNetworkAccessManager
-from qgis.PyQt.QtCore import QObject, Qt, QTimer
-from qgis.PyQt.QtWidgets import QAction, QMenu, QMessageBox, QTreeWidgetItemIterator
-
-from ..connection.shogunressource import ShogunRessource
-from .dialog_bases.connectdlg import ConnectDialog
-from .dialog_bases.dockwidget import DockWidget
-from .editoritems import ApplicationItem, EditorItem, EditorTopItem, LayerItem, QgisLayerItem
-
-__author__ = "ntreff"
-__date__ = "July 2025"
-
-
-class Editor(QObject):
- """This class controls all plugin-related GUI elements."""
-
- def __init__(self, iface):
- """initialize the GUI control"""
- QObject.__init__(self)
- self.iface = iface
-
- self.dock = DockWidget()
- self.connectdlg = ConnectDialog()
- self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dock)
- self.dock.newConnectionButton.clicked.connect(
- lambda: self.showDialog(self.connectdlg)
- )
- self.connectdlg.okButton.clicked.connect(self.setupNewConnection)
- self.topitem = EditorTopItem()
- self.connections = []
- self.dock.treeWidget.addTopLevelItem(self.topitem)
- self.dock.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
- self.dock.treeWidget.customContextMenuRequested.connect(self.on_context_menu)
- self.dock.treeWidget.itemDoubleClicked.connect(self.on_tree_item_double_clicked)
-
- self.timer = QTimer()
-
- """
- WORKAROUND:
- When performing requests with self.http, it will call the
- QgsNetworkAccessManager.instance(). For some reason
- QgsNetworkAccessManager.instance() is connected with the QtCore SIGNAL
- 'authenticationRequired' (inherited from QNetworkAccessManager) to a
- method where a dialog in QGIS pops up asking for the users credentials
- (when working with Basic Auth). We disable the signal here as the
- case of wrong identification credentials is treated separately
- in def: checkConnection(self)
- """
- try:
- QgsNetworkAccessManager.instance().authenticationRequired.disconnect()
- except Exception as e:
- print('Error occurred: ' + str(e))
- pass
-
- def on_context_menu(self, point):
-
- item = self.dock.treeWidget.itemAt(point)
- if item is None:
- return
- if item.actiontype is None:
- return
- actionDict = {
- "application": (
- "Copy Application",
- "Load all layers to QGIS",
- "Application Settings",
- "View Application in web browser",
- "Delete Application",
- ),
- "layer": ("Add Layer to QGIS", "Layer Settings", "Delete Layer"),
- "qgisLayerReference": ("Upload New Style", "Apply Original Style"),
- "applicationsItem": ("Create New Application", "Refresh Applications"),
- "layersItem": ("Upload New Layer from QGIS", "Refresh Layers"),
- "connection": ("Refresh Connection", "Remove Connection"),
- "topitem": ["New Connection"],
- }
-
- actions = actionDict[item.actiontype]
- menu = QMenu()
- acts = []
- for actionName in actions:
- action = QAction(actionName, None)
- self.connectAction(action, actionName, item)
- acts.append(action)
- menu.addActions(acts)
- point = self.dock.treeWidget.mapToGlobal(point)
- menu.exec_(point)
-
- # this could be re-written when refactoring:
- def connectAction(self, action, actionName, item):
- if actionName == "Copy Application":
- action.triggered.connect(item.copyApplication)
- elif actionName == "View Application in web browser":
- action.triggered.connect(
- lambda: item.ressource.viewApplicationOnline(item.id)
- )
- elif actionName == "New Connection":
- action.triggered.connect(lambda: self.showDialog(self.connectdlg))
- elif actionName == "Application Settings":
- action.triggered.connect(lambda: self.showDialog(item))
- elif actionName == "Layer Settings":
- action.triggered.connect(lambda: self.showDialog(item))
- elif actionName == "Remove Connection":
- action.triggered.connect(lambda: self.removeConnection(item))
- elif actionName == "Refresh Connection":
- action.triggered.connect(lambda: self.refreshConnection(item))
- elif actionName == "Add Layer to QGIS":
- action.triggered.connect(lambda: item.addQgsLayer(self.iface))
- elif actionName == "Upload New Style":
- action.triggered.connect(lambda: self.uploadStyle(item))
- elif actionName == "Apply Original Style":
- action.triggered.connect(lambda: self.downloadStyle(item))
- elif actionName == "Load all layers to QGIS":
- action.triggered.connect(lambda: self.loadAllAppLayers(item))
- elif actionName == "Create New Application":
- action.triggered.connect(lambda: item.createNewApplication(self.iface))
- elif actionName == "Upload New Layer from QGIS":
- action.triggered.connect(lambda: item.createNewLayer(self.iface))
- elif actionName == "Delete Layer":
- action.triggered.connect(item.deleteLayer)
- elif actionName == "Delete Application":
- action.triggered.connect(item.deleteApplication)
- elif actionName == "Refresh Applications":
- action.triggered.connect(item.update)
- elif actionName == "Refresh Layers":
- action.triggered.connect(item.update)
-
- def on_tree_item_double_clicked(self, item):
- if isinstance(item, QgisLayerItem):
- try:
- self.iface.showLayerProperties(item.layer)
- except Exception as e:
- print('Error occurred: ' + str(e))
- pass
-
- def showDialog(self, item):
- if not isinstance(item, ConnectDialog):
- if item.dlg is None:
- if isinstance(item, ApplicationItem):
- item.createStaticSettings(self.iface)
- item.populateSettings()
- elif isinstance(item, LayerItem):
- item.createStaticSettings()
- item.populateSettings()
- dialog = item.dlg
- else:
- dialog = self.connectdlg
-
- dialog.show()
- try:
- dialog.setWindowState(Qt.WindowActive)
- dialog.activateWindow()
- except Exception as e:
- print('Error occurred: ' + str(e))
- pass
-
- def setupNewConnection(self):
- # if the connect button gets clicked two times quickly it calls a new
- # test request before the old one is finished and making qgis crash
- # therefore we pause the signal for 1,5 seconds
- self.connectdlg.okButton.blockSignals(True)
- self.timer.timeout.connect(lambda: self.connectdlg.okButton.blockSignals(False))
- self.timer.start(1500)
-
- url = self.connectdlg.urlIn.text()
- name = self.connectdlg.nameIn.text()
- user = self.connectdlg.userIn.text()
- pw = self.connectdlg.passwordIn.text()
-
- if len(url) == 0 or len(name) == 0:
- self.showWarning(self.connectdlg, "Please fill in all necessary fields")
- self.connectdlg.show()
- return
-
- if name in self.connections:
- self.showWarning(
- self.connectdlg,
- "A connection with that name "
- "exists already, please fill in a different name",
- )
- self.connectdlg.show()
- return
-
- newRessource = ShogunRessource(self.iface, url, name, user, pw)
- connectionOk = newRessource.checkConnection()
- if not connectionOk[0]:
- self.showWarning(self.connectdlg, connectionOk[1])
- self.connectdlg.show()
- return
-
- result = newRessource.updateData()
- if not result:
- self.showWarning(
- self.connectdlg, "Error: Could not retrieve all data from Shogun"
- )
-
- self.connectdlg.hide()
-
- newConnectionItem = EditorItem(newRessource)
- self.connections.append(name)
- self.topitem.addChild(newConnectionItem)
- self.topitem.setExpanded(True)
- newConnectionItem.applicationsitem.sortChildren(0, Qt.AscendingOrder)
- newConnectionItem.layersitem.sortChildren(0, Qt.AscendingOrder)
- self.expandEditorTree(newConnectionItem)
-
- def removeConnection(self, item):
- if item.name in self.connections:
- self.connections.remove(item.name)
- self.topitem.removeChild(item)
-
- def refreshConnection(self, item):
- item.ressource.updateData()
- item.update()
- self.topitem.setExpanded(True)
- self.expandEditorTree(item)
-
- def showWarning(self, parent, text):
- QMessageBox.warning(parent, "Warning", text, QMessageBox.Ok)
-
- def uploadStyle(self, item):
- success = item.uploadStyle()
- if success:
- msg = "New style of layer " + item.parentShogunLayer.name
- msg += " was successfully uploaded to Shogun."
- self.iface.messageBar().pushSuccess("Success", msg)
- else:
- msg = "New style of layer " + item.parentShogunLayer.name
- msg += " could not be uploaded to Shogun."
- self.iface.messageBar().pushCritical("Error", msg)
-
- def downloadStyle(self, item):
- success = item.downloadStyle()
- if not success:
- msg = "Could not download Style for layer"
- self.iface.messageBar().pushCritical("Error", msg)
-
- def loadAllAppLayers(self, item):
- layerIds, shogunConnectionItem = item.getAllAppLayersById()
- for layer in shogunConnectionItem.layersitem.layerlist:
- if layer.id in layerIds:
- layer.addQgsLayer(self.iface)
-
- def expandEditorTree(self, connectionItem):
- iterator = QTreeWidgetItemIterator(connectionItem)
- val = iterator.value()
- while val:
- val.setExpanded(True)
- iterator += 1
- val = iterator.value()
diff --git a/gui/editoritems.py b/gui/editoritems.py
deleted file mode 100644
index cb18df6..0000000
--- a/gui/editoritems.py
+++ /dev/null
@@ -1,1141 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-(c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/
- This code is licensed under the GPL 2.0 license.
-"""
-
-import os
-import sys
-
-from qgis.core import QgsLayerItem, QgsMapLayer, QgsPointXY, QgsProject, QgsRectangle, QgsWkbTypes
-from qgis.PyQt.QtCore import Qt
-from qgis.PyQt.QtGui import QFont, QIcon
-from qgis.PyQt.QtWidgets import QMessageBox, QTreeWidgetItem
-
-from ..layerutils import createLayer, prepareLayerForUpload
-from .dialog_bases.applicationSettings import ApplicationSettingsDialog
-from .dialog_bases.layerSettings import LayerSettingsDialog, UploadLayerDialog
-
-__author__ = "ntreff"
-__date__ = "July 2025"
-
-PYTHON_VERSION = sys.version_info[0]
-
-"""This file contains all classes used in the QTreeWidget representating the
-structure of the connected shogun2-webapp ressource"""
-
-# TODO: the classes LayerItem and ApplicationItem could be merged or inherit
-# from a common abstract class, the same is LayerSettingsDialog and
-# ApplicationSettingsDialog. Note when refactoring for future release
-
-
-class TreeItem(QTreeWidgetItem):
- """This class works as a kind of 'abstract class' from which all other
- classes in this module inherit"""
-
- def __init__(self, icon=None, text=None):
- QTreeWidgetItem.__init__(self)
- self.setText(0, text)
- if icon is not None:
- if isinstance(icon, QIcon):
- self.setIcon(0, icon)
- elif isinstance(icon, str):
- iconPath = ":/plugins/shoguneditor/" + icon
- self.setIcon(0, QIcon(iconPath))
- self.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
- self.actiontype = None
-
-
-class EditorTopItem(TreeItem):
- def __init__(self):
- TreeItem.__init__(
- self, "shogun-logo-50x50px-round-blue.png", "Shogun Connections"
- )
- self.actiontype = "topitem"
- font = QFont("Arial", 14)
- font.setBold(True)
- self.setFont(0, font)
-
-
-class EditorItem(TreeItem):
- def __init__(self, shogunRessource):
- self.ressource = shogunRessource
- self.name = shogunRessource.name
- TreeItem.__init__(self, "shogun-logo-25x-25px.png", self.name)
- self.isConnected = False
- self.actiontype = "connection"
- font = QFont("Arial", 12)
- font.setBold(True)
- self.setFont(0, font)
- self.populateTree(shogunRessource)
-
- def disconnectSignals(self):
- if self.layersitem is not None:
- print("disconnecting signals for layersitem")
- # TODO: Check - code seems to be unreachable
- # for layer in self.layersitem.layerlist:
- # try:
- # QgsProject.instance().layerRemoved.disconnect(layer.updateLayerList)
- # except:
- # pass
-
- def update(self):
- for x in [self.applicationsitem, self.layersitem]:
- self.removeChild(x)
- self.populateTree(self.ressource)
-
- def populateTree(self, shogunRessource):
- self.applicationsitem = ApplicationsItem(shogunRessource)
- self.layersitem = LayersItem(shogunRessource)
-
- self.addChildren([self.applicationsitem, self.layersitem])
-
-
-class ApplicationsItem(TreeItem):
- def __init__(self, ressource):
- TreeItem.__init__(self, "applications-logo.png", "Applications")
- self.ressource = ressource
- self.applications = self.ressource.getApplicationIdsAndNames()
- self.applicationlist = []
- self.actiontype = "applicationsItem"
- font = QFont("Arial", 10)
- font.setBold(True)
- self.setFont(0, font)
- self.populate()
-
- def createNewApplication(self, iface):
- self.newApplication = ApplicationItem(None, "", self.ressource)
- self.newApplication.createStaticSettings(iface)
- self.dlg = self.newApplication.dlg
- self.dlg.setEditState(True)
- self.dlg.editCheckBox.setHidden(True)
- self.dlg.pushButtonOk.setText("Create")
- self.dlg.pushButtonOk.clicked.disconnect(self.dlg.hide)
- self.dlg.pushButtonOk.clicked.connect(self.checkNewApplicationSettings)
- self.dlg.pushButtonCancel.clicked.disconnect(self.newApplication.stopEditing)
- self.dlg.pushButtonCancel.clicked.connect(self.dlg.hide)
- self.dlg.pushButtonCancel.setHidden(False)
-
- for toolbox in self.dlg.tools.values():
- toolbox.setChecked(True)
-
- exampleHomeview = {
- "mapconfig": {
- "id": None,
- "center": {"x": 0.0, "y": 0.0},
- "zoom": 18,
- "resolutions": [
- 156543.03390625,
- 78271.516953125,
- 39135.7584765625,
- 19567.87923828125,
- 9783.939619140625,
- 4891.9698095703125,
- 2445.9849047851562,
- 1222.9924523925781,
- 611.4962261962891,
- 305.74811309814453,
- 152.87405654907226,
- 76.43702827453613,
- 38.218514137268066,
- 19.109257068634033,
- 9.554628534317017,
- 4.777314267158508,
- 2.388657133579254,
- 1.194328566789627,
- 0.5971642833948135,
- ],
- }
- }
-
- self.newApplication.homeview = exampleHomeview
- self.newApplication.setQgsExtent(iface)
- self.dlg.origExtentButton.setEnabled(False)
- self.dlg.jumpButtonOrig.setEnabled(False)
- self.dlg.homeviewZoomBox.setMaximum(18)
-
- allLayers = self.ressource.getLayerIdsAndNames()
- self.dlg.layerlistwidget.populateList(allLayers)
- self.dlg.layertreewidget.setupNewTree()
-
- self.dlg.newAppCreation()
-
- self.dlg.show()
-
- def checkNewApplicationSettings(self):
- name = self.dlg.nameEdit.text()
-
- if len(name) == 0:
- self.dlg.nameEdit.setStyleSheet("QLineEdit { background-color: #ff3333; }")
- self.dlg.warnLabel.setHidden(False)
- return
- else:
- self.dlg.nameEdit.setStyleSheet("QLineEdit { border-color : #000000; }")
- self.dlg.warnLabel.setHidden(True)
-
- newhomeview = self.newApplication.getHomeViewChanges()
- if newhomeview is None:
- newhomeview = self.newApplication.homeview["mapconfig"]
- data = {
- "id": None,
- "name": name,
- "description": self.dlg.descriptionEdit.text(),
- "language": self.dlg.languageBox.currentText(),
- "isPublic": self.dlg.boxPublic.isChecked(),
- "isActive": self.dlg.boxActive.isChecked(),
- "activeTools": [
- x["id"] for x in self.newApplication.getActiveToolsChanges()
- ],
- "projection": "EPSG:3857",
- "center": {
- "x": newhomeview["center"]["x"],
- "y": newhomeview["center"]["y"],
- },
- "zoom": newhomeview["zoom"],
- "layerTree": 4535,
- }
-
- self.dlg.hide()
- uploaded = self.ressource.uploadNewApplication(data)
- if uploaded:
- self.update()
-
- def populate(self):
- for app in self.applications:
- item = ApplicationItem(app[0], app[1], self.ressource)
- self.addChild(item)
- self.applicationlist.append(item)
- self.sortChildren(0, Qt.AscendingOrder)
-
- def update(self):
- self.applications = self.ressource.getApplicationIdsAndNames(reload=True)
- for item in self.applicationlist:
- self.removeChild(item)
- self.applicationlist = []
- for app in self.applications:
- item = ApplicationItem(app[0], app[1], self.ressource)
- self.addChild(item)
- self.applicationlist.append(item)
- self.sortChildren(0, Qt.AscendingOrder)
-
-
-class ApplicationItem(TreeItem):
- def __init__(self, _id, name, ressource):
- TreeItem.__init__(self, None, name)
- self.actiontype = "application"
- self.id = _id
- self.name = name
- self.dlg = None
- self.ressource = ressource
- self.activeTools = []
- self.homeview = None
- self.settings = {
- "id": 0,
- "name": "",
- "description": "",
- "language": "",
- "open": False,
- "active": False,
- "activeTools": [],
- }
-
- def createStaticSettings(self, iface):
- self.dlg = ApplicationSettingsDialog()
- self.dlg.setEditState(False)
- self.dlg.pushButtonOk.clicked.connect(self.dlg.hide)
- self.dlg.editCheckBox.clicked.connect(self.startEditing)
- self.dlg.pushButtonCancel.clicked.connect(self.stopEditing)
-
- self.iface = iface
-
- self.dlg.origExtentButton.clicked.connect(self.setOriginalExtent)
- self.dlg.qgsExtentButton.clicked.connect(lambda: self.setQgsExtent())
- self.dlg.jumpButtonOrig.clicked.connect(lambda: self.zoomToOrigExtent())
-
- self.dlg.homeviewZoomBox.valueChanged.connect(lambda: self.zoomToNewExtent())
- self.dlg.homeviewCenterEditX.textEdited.connect(lambda: self.zoomToNewExtent())
- self.dlg.homeviewCenterEditY.textEdited.connect(lambda: self.zoomToNewExtent())
- self.dlg.jumpButtonNew.clicked.connect(lambda: self.zoomToNewExtent())
-
- def setOriginalExtent(self):
- disableSignals = [self.dlg.homeviewZoomBox, self.dlg.homeviewCenterEditX]
- disableSignals.append(self.dlg.homeviewCenterEditY)
- disableSignals.append(self.dlg.jumpButtonNew)
-
- for qobject in disableSignals:
- qobject.blockSignals(True)
-
- self.dlg.homeviewCenterEditX.setText(
- str(self.homeview["mapconfig"]["center"]["x"])
- )
- self.dlg.homeviewCenterEditY.setText(
- str(self.homeview["mapconfig"]["center"]["y"])
- )
- self.dlg.homeviewZoomBox.setValue(self.homeview["mapconfig"]["zoom"])
- self.populateExtentEdits(self.origExtRect)
-
- for qobject in disableSignals:
- qobject.blockSignals(False)
-
- def populateExtentEdits(self, extent):
- self.dlg.extentEdits[0].setText(str(extent.xMinimum()))
- self.dlg.extentEdits[1].setText(str(extent.yMinimum()))
- self.dlg.extentEdits[2].setText(str(extent.xMaximum()))
- self.dlg.extentEdits[3].setText(str(extent.yMinimum()))
-
- def setQgsExtent(self):
- rect = self.iface.mapCanvas().extent()
- self.populateExtentEdits(rect)
- center = self.iface.mapCanvas().center()
- self.dlg.homeviewCenterEditX.setText(str(center.x()))
- self.dlg.homeviewCenterEditY.setText(str(center.y()))
-
- zoom = self.iface.mapCanvas().mapUnitsPerPixel()
- # get shogun resolutions as enumerated list
- resolutions = list(enumerate(self.homeview["mapconfig"]["resolutions"]))
- cursor = resolutions[0]
- for res in resolutions:
- if abs(zoom - res[1]) < abs(zoom - cursor[1]):
- cursor = res
- # cursor is now the Shogun resolution closest to the qgis resolution
-
- self.dlg.homeviewZoomBox.setValue(cursor[0])
-
- def zoomToOrigExtent(self):
- # first set the original homeview center as the new canvas center
- center = self.homeview["mapconfig"]["center"]
- point = QgsPointXY(center["x"], center["y"])
- self.iface.mapCanvas().setCenter(point)
-
- zoomlvl = self.homeview["mapconfig"]["zoom"]
- zoom = self.homeview["mapconfig"]["resolutions"][zoomlvl]
-
- currentResolution = self.iface.mapCanvas().mapUnitsPerPixel()
- if currentResolution != zoom:
- diff = zoom / currentResolution
- self.iface.mapCanvas().zoomByFactor(diff)
-
- self.iface.mapCanvas().refresh()
-
- def zoomToNewExtent(self):
- x = float(self.dlg.homeviewCenterEditX.text())
- y = float(self.dlg.homeviewCenterEditY.text())
- point = QgsPointXY(x, y)
- self.iface.mapCanvas().setCenter(point)
-
- zoomlvl = self.dlg.homeviewZoomBox.value()
- zoom = self.homeview["mapconfig"]["resolutions"][zoomlvl]
-
- currentResolution = self.iface.mapCanvas().mapUnitsPerPixel()
- if currentResolution != zoom:
- diff = zoom / currentResolution
- self.iface.mapCanvas().zoomByFactor(diff)
-
- self.iface.mapCanvas().refresh()
-
- def populateSettings(self):
- # get application settings as a dict representing the json
- settings = self.ressource.getApplicationAttrsById(self.id)
- for attr in self.settings:
- self.settings[attr] = settings[attr]
-
- # set the general settings
- self.dlg.nameEdit.setText(settings["name"])
- self.dlg.descriptionEdit.setText(settings["description"])
- self.dlg.languageBox.setCurrentIndex(
- self.dlg.languageBox.findText(settings["language"])
- )
- self.dlg.boxPublic.setChecked(settings["open"])
- self.dlg.boxActive.setChecked(settings["active"])
-
- # set the configured homeview
- self.mapConfigId = settings["viewport"]["subModules"][0]["subModules"][0][
- "mapConfig"
- ]["id"]
- self.extentId = settings["viewport"]["subModules"][0]["subModules"][0][
- "mapConfig"
- ]["extent"]["id"]
- # getHomeviewByIds returns homeview as dict {'mapconfig':XY,'extent':XY}
- self.homeview = self.ressource.getHomeviewByIds(self.mapConfigId, self.extentId)
-
- self.dlg.homeviewZoomBox.setMaximum(
- len(self.homeview["mapconfig"]["resolutions"]) - 1
- )
- origExtent = list(self.homeview["extent"]["lowerLeft"].values()) + list(
- self.homeview["extent"]["upperRight"].values()
- )
- self.origExtRect = QgsRectangle(
- origExtent[0], origExtent[1], origExtent[2], origExtent[3]
- )
- self.setOriginalExtent()
-
- self.epsg = self.homeview["mapconfig"]["projection"]
- if PYTHON_VERSION >= 3:
- currentQgsCrs = QgsProject.instance().crs().authid()
- else:
- currentQgsCrs = (
- self.iface.mapCanvas().mapRenderer().destinationCrs().authid()
- )
-
- if self.epsg != currentQgsCrs:
- self.dlg.showEpsgWarning(currentQgsCrs, self.epsg)
- else:
- self.dlg.hideEpsgWarning()
-
- # set the application tools
- self.activeTools = [{"id": tool["id"]} for tool in settings["activeTools"]]
- for tool in self.activeTools:
- self.dlg.tools[tool["id"]].setChecked(True)
-
- # populate the layer tree and the table of available layers
- layerTree = settings["layerTree"]
- self.dlg.layertreewidget.populateTree(layerTree)
-
- allLayers = self.ressource.getLayerIdsAndNames()
- self.dlg.layerlistwidget.populateList(allLayers)
-
- # populate the permissions tables
- self.userPermissions = self.ressource.getObjectPermissions(
- self.id, "Application", "User"
- )
- self.groupPermissions = self.ressource.getObjectPermissions(
- self.id, "Application", "UserGroup"
- )
-
- if not self.userPermissions["success"] or not self.groupPermissions["success"]:
- self.dlg.noPermissionAccess()
- else:
- self.dlg.populateTable("users", self.userPermissions["data"]["permissions"])
- self.dlg.populateTable(
- "groups", self.groupPermissions["data"]["permissions"]
- )
-
- def getAllAppLayersById(self):
- rootConnectionItem = self.parent().parent()
- settings = self.ressource.getApplicationAttrsById(self.id)
- listOfIds = []
- layerTree = settings["layerTree"]
-
- # for reasons of simplicity we only search for belonging layers down to the
- # third level of the tree
- if layerTree["leaf"]:
- listOfIds.append(layerTree["layer"]["id"])
- return listOfIds, rootConnectionItem
- else:
- children = layerTree["children"]
- for child in children:
- if child["leaf"]:
- listOfIds.append(child["layer"]["id"])
- else:
- children = child["children"]
- for child in children:
- if child["leaf"]:
- listOfIds.append(child["layer"]["id"])
- else:
- children = child["children"]
- for child in children:
- if child["leaf"]:
- listOfIds.append(child["layer"]["id"])
- return listOfIds, rootConnectionItem
-
- def getAllChanges(self):
- allChanges = {}
- if self.getGeneralChanges() is not None:
- allChanges["general"] = self.getGeneralChanges()
-
- if self.getActiveToolsChanges() is not None:
- if "general" not in allChanges:
- allChanges["general"] = {}
- allChanges["general"]["activeTools"] = self.getActiveToolsChanges()
-
- if self.getHomeViewChanges() is not None:
- allChanges["homeview"] = self.getHomeViewChanges()
-
- layerTreeChanges = self.dlg.layertreewidget.getLayerTreeChanges()
- if layerTreeChanges is not None:
- allChanges["layerTree"] = layerTreeChanges
-
- userPermissionChanges = self.getPermissionChanges("User")
- if userPermissionChanges is not None:
- allChanges["permissions"] = {"User": userPermissionChanges}
- groupPermissionChanges = self.getPermissionChanges("UserGroup")
- if groupPermissionChanges is not None:
- if "permissions" not in allChanges:
- allChanges["permissions"] = {}
- allChanges["permissions"]["UserGroup"] = groupPermissionChanges
-
- return allChanges
-
- def getGeneralChanges(self):
- changes = {}
- if self.dlg.nameEdit.text() != self.settings["name"]:
- changes["name"] = self.dlg.nameEdit.text()
- if self.dlg.descriptionEdit.text() != self.settings["description"]:
- changes["description"] = self.dlg.descriptionEdit.text()
- if self.dlg.languageBox.currentText() != self.settings["language"]:
- changes["languae"] = self.dlg.languageBox.currentText()
- if self.dlg.boxPublic.isChecked() != self.settings["open"]:
- changes["open"] = self.dlg.boxPublic.isChecked()
- if self.dlg.boxActive.isChecked() != self.settings["active"]:
- changes["active"] = self.dlg.boxActive.isChecked()
-
- if len(changes) == 0:
- return None
- else:
- return changes
-
- def getActiveToolsChanges(self):
- toolsCheckedInGui = []
- for toolid in self.dlg.tools.keys():
- if self.dlg.tools[toolid].isChecked():
- toolsCheckedInGui.append(toolid)
-
- # convert the lists to set for in case they have different orders
- if set(toolsCheckedInGui) != set([tool["id"] for tool in self.activeTools]):
- return [{"id": toolid} for toolid in toolsCheckedInGui]
- else:
- return None
-
- def getHomeViewChanges(self):
- zoom = self.dlg.homeviewZoomBox.value()
- x = float(self.dlg.homeviewCenterEditX.text())
- y = float(self.dlg.homeviewCenterEditY.text())
- conf = self.homeview["mapconfig"]
- newhomeview = None
-
- if conf["zoom"] != zoom or conf["center"]["x"] != x or conf["center"]["y"] != y:
- newhomeview = {
- "id": self.homeview["mapconfig"]["id"],
- "zoom": zoom,
- "center": {"x": x, "y": y},
- }
- return newhomeview
-
- def getPermissionChanges(self, _type):
- if _type == "User":
- permissions = self.userPermissions["data"]
- table = self.dlg.usertabel
- elif _type == "UserGroup":
- permissions = self.groupPermissions["data"]
- table = self.dlg.groupstabel
- else:
- return
- oldPermissionList = permissions["permissions"]
- newPermissionList = []
- for entry in oldPermissionList:
- name = entry["displayTitle"]
- row = None
- for x in range(table.rowCount()):
- if table.verticalHeaderItem(x).text() == name:
- row = x
- break
- if row is None:
- return
-
- currentPermissionsInTable = []
- for p in enumerate(["READ", "UPDATE", "DELETE"]):
- if table.item(row, p[0]).checkState() == 2:
- currentPermissionsInTable.append(p[1])
-
- if entry["permissions"] is None:
- oldPermissions = []
- else:
- oldPermissions = entry["permissions"]["permissions"]
- if oldPermissions == ["ADMIN"]:
- oldPermissions = ["READ", "UPDATE", "DELETE"]
-
- if set(currentPermissionsInTable) != set(oldPermissions):
- newEntry = {
- "targetEntity": permissions["targetEntity"],
- "permissions": [
- {
- "permissions": {"permissions": currentPermissionsInTable},
- "targetEntity": entry["targetEntity"],
- }
- ],
- }
- newPermissionList.append(newEntry)
- if len(newPermissionList) == 0:
- newPermissionList = None
- return newPermissionList
-
- def startEditing(self):
- self.dlg.setEditState(True)
- self.dlg.editCheckBox.clicked.disconnect(self.startEditing)
- self.dlg.pushButtonOk.clicked.disconnect(self.dlg.hide)
- self.dlg.editCheckBox.clicked.connect(self.stopEditing)
- self.dlg.pushButtonOk.clicked.connect(self.saveChanges)
-
- def stopEditing(self):
- changes = self.getAllChanges()
- if len(changes) > 0:
- warn = QMessageBox.warning(
- self.dlg,
- "Warning",
- "All changes will be" " lost. Continue?",
- QMessageBox.Cancel,
- QMessageBox.Ok,
- )
- if warn == QMessageBox.Ok:
- self.populateSettings()
- else:
- self.dlg.editCheckBox.setChecked(True)
- return
- self.stoppedEditing()
-
- def saveChanges(self):
- changes = self.getAllChanges()
- if len(changes) > 0:
- conf = QMessageBox.warning(
- self.dlg,
- "Confirm",
- "Please confirm you" " want to save the changes",
- QMessageBox.Cancel,
- QMessageBox.Ok,
- )
- if conf == QMessageBox.Ok:
- if "general" in changes:
- # 'changes' will be sent as the data, therefore append the id
- # as a key, so that the data will be recognized
- changes["general"]["id"] = self.id
- responseCode = self.ressource.editApplication(
- self.id, changes["general"]
- )
- # update the name in the tree view:
- if ("name" in changes["general"] and responseCode > 199 and responseCode < 299):
- self.name = changes["general"]["name"]
- self.setText(0, self.name)
-
- if "homeview" in changes:
- self.ressource.editMapConfig(self.mapConfigId, changes["homeview"])
-
- # update the homeview after the changes
- self.ressource.updateExtentsAndMapConfigs()
- self.homeview = self.ressource.getHomeviewByIds(
- self.mapConfigId, self.extentId
- )
-
- if "layerTree" in changes:
- deletedItemIds = changes["layerTree"]["deleteItems"]
- responses = []
- if len(deletedItemIds) > 0:
- for _id in deletedItemIds:
- responses.append(self.ressource.deleteLayerTreeItem(_id))
- newItems = changes["layerTree"]["newItems"]
- if len(newItems) > 0:
- for item in newItems:
- responses.append(self.ressource.createLayerTreeItem(item))
- changeItems = changes["layerTree"]["changeItems"]
- if len(changeItems) > 0:
- for item in changeItems:
- _id = item["id"]
- responses.append(
- self.ressource.updateLayerTreeItem(_id, item)
- )
- res = 200
- for x in responses:
- if not 199 < x < 210:
- res = x
- self.ressource.userInfo(res, "Layertree", "updated")
-
- if "permissions" in changes:
- responses = 200
- for x in changes["permissions"].keys():
- for entry in changes["permissions"][x]:
- responseBool = self.ressource.editObjectPermission(
- self.id, "ProjectApplication", x, entry
- )
- if not responseBool:
- responses = 400
-
- self.userPermissions = self.ressource.getObjectPermissions(
- self.id, "Application", "User"
- )
- self.groupPermissions = self.ressource.getObjectPermissions(
- self.id, "Application", "UserGroup"
- )
- self.ressource.userInfo(
- responses, "Application permissions", "updated"
- )
-
- # update the class internal copy of the general settings
- newSettings = self.ressource.updateSingleApplication(self.id)
- for attr in self.settings:
- self.settings[attr] = newSettings[attr]
- self.dlg.layertreewidget.populateTree(newSettings["layerTree"])
- # if the user clicked 'cancel', just return and stay in edit mode:
- else:
- return
-
- else:
- QMessageBox.information(self.dlg, "Note", "No changes were found", QMessageBox.Ok)
- self.stoppedEditing()
-
- def stoppedEditing(self):
- self.dlg.editCheckBox.clicked.disconnect(self.stopEditing)
- self.dlg.pushButtonOk.clicked.disconnect(self.saveChanges)
- self.dlg.pushButtonOk.clicked.connect(self.dlg.hide)
- self.dlg.editCheckBox.clicked.connect(self.startEditing)
- self.dlg.editCheckBox.setChecked(False)
- self.dlg.setEditState(False)
-
- def deleteApplication(self):
- txt = "Please confirm you want to permanently delete "
- txt += "the selected application in Shogun"
- conf = QMessageBox.warning(
- self.dlg, "Confirm", txt, QMessageBox.Cancel, QMessageBox.Ok
- )
- if conf == QMessageBox.Ok:
- self.ressource.deleteApplication(self.id)
- self.ressource.updateApplications()
- self.parent().update()
-
- def copyApplication(self):
- self.ressource.copyApplication(self.id, self.name)
- self.parent().update()
-
-
-class LayersItem(TreeItem):
- def __init__(self, ressource):
- TreeItem.__init__(self, "layers-logo.png", "Layers")
- self.ressource = ressource
- self.layers = self.ressource.getLayerIdsAndNames()
- self.layerlist = []
- self.actiontype = "layersItem"
- font = QFont("Arial", 10)
- font.setBold(True)
- self.setFont(0, font)
- self.populate()
-
- def populate(self):
- for layer in self.layers:
- item = LayerItem(layer[0], layer[1], layer[2], layer[3], self.ressource)
- self.addChild(item)
- self.layerlist.append(item)
- self.sortChildren(0, Qt.AscendingOrder)
-
- def update(self):
- # update the list of current shogun layers:
- self.layers = self.ressource.getLayerIdsAndNames(reload=True)
- layerIdList = [layer[0] for layer in self.layers]
- # remove every item that is not in the updated list:
- for layer in self.layerlist:
- if layer.id not in layerIdList:
- self.removeChild(layer)
- self.layerlist.remove(layer)
-
- # check if new layers appeared and append them:
- currentLayerIdList = [layer.id for layer in self.layerlist]
- for layer in self.layers:
- if layer[0] not in currentLayerIdList:
- item = LayerItem(layer[0], layer[1], layer[2], layer[3], self.ressource)
- self.addChild(item)
- self.layerlist.append(item)
- self.sortChildren(0, Qt.AscendingOrder)
-
- def createNewLayer(self, iface):
- self.uploadDialog = UploadLayerDialog()
- # mapLayers = QgsMapLayerRegistry.instance().mapLayers().values()
- # self.uploadDialog.layerBox.addItems([layer.name() for layer in mapLayers])
- self.uploadDialog.uploadButton.clicked.connect(self.uploadLayerAction)
- self.uploadDialog.show()
-
- def uploadLayerAction(self):
- layer = self.uploadDialog.layerBox.currentLayer()
- # layerutils
- pathToZipFile, pathToTempDir = prepareLayerForUpload(layer, self.uploadDialog)
- if pathToZipFile:
- if layer.type() == QgsMapLayer.VectorLayer:
- layer_type = "Vector"
- else:
- if layer.providerType() == "wms":
- success = self.ressource.publishWmsLayer()
- if success:
- self.uploadDialog.log(
- "WMS layer " + layer.name() + ""
- "was successfully published"
- )
- self.update()
- else:
- self.uploadDialog.log(
- "Publishing WMS "
- "layer " + layer.name() + " was not successfull"
- )
-
- return
- else:
- layer_type = "Raster"
- success = self.ressource.uploadLayer(pathToZipFile, layer_type)
- if success:
- self.uploadDialog.log(
- "Layer " + layer.name() + " was successfully uploaded"
- )
- self.update()
- else:
- self.uploadDialog.log(
- "Uploading layer " + layer.name() + " was not successfull"
- )
- # after the process has finished we delete the created zipfile and
- # temporary directory for cleaning up
- try:
- os.remove(pathToZipFile)
- os.rmdir(pathToTempDir)
- except Exception as e:
- print('Error occurred: ' + str(e))
- pass
-
-
-class LayerItem(TreeItem):
- def __init__(self, _id, name, datatype, source, ressource):
-
- # unfortunately there is no option to retrieve the layer geometry (i. e.
- # point/line/polygon for vectorlayers) just from the json object.
- # Therefore they get a default polygon icon which will be updated as soon as
- # layer is added as WFS to QGIS - see def addQgsLayer()
- if datatype == "Raster":
- self.icon = QgsLayerItem.iconRaster()
- elif datatype == "Vector":
- self.icon = QgsLayerItem.iconPolygon()
- else:
- self.icon = QgsLayerItem.iconDefault()
- TreeItem.__init__(self, self.icon, name)
- self.id = _id
- self.actiontype = "layer"
- self.ressource = ressource
- self.dlg = None
- self.name = name
- self.source = source
- self.datatype = datatype
- self.qgisLayers = []
- self.settings = {
- "name": "",
- "appearance": {"hoverTemplate": None, "opacity": 1},
- }
-
- QgsProject.instance().layerRemoved.connect(self.updateLayerList)
-
- def updateLayerList(self):
- if self.qgisLayers == []:
- return
- currentMapLayers = QgsProject.instance().mapLayers()
- for qgisLayerItem in self.qgisLayers:
- if qgisLayerItem.id not in currentMapLayers:
- self.qgisLayers.remove(qgisLayerItem)
- self.removeChild(qgisLayerItem)
-
- def addQgsLayer(self, iface):
- currentCrs = iface.mapCanvas().mapSettings().destinationCrs()
- if currentCrs.isValid():
- epsg = currentCrs.authid()
- else:
- epsg = "EPSG:3857"
- # layerutils
- layer = createLayer(self, epsg)
- if not layer:
- return
- qgisLayerItem = QgisLayerItem(layer, self, self.ressource)
- self.qgisLayers.append(qgisLayerItem)
- self.addChild(qgisLayerItem)
- self.setExpanded(True)
-
- # for an odd reason there appears a warning below the layer if it is a
- # wms layer from shogun - workaround to hide this:
- if layer.dataProvider().name() == "wms" and self.source["url"].startswith(
- "/shogun2-webapp/"
- ):
- root = QgsProject.instance().layerTreeRoot()
- layerNode = root.findLayer(layer.id())
- layerNode.setExpanded(False)
-
- # when the layer has loaded for the first time and it's icon
- # is still on iconDefault, update the icon with the correct geometry
- if not self.datatype == "Raster":
- if layer.type() == QgsMapLayer.VectorLayer:
- geom = layer.geometryType()
-
- if geom == QgsWkbTypes.PointGeometry:
- self.icon = QgsLayerItem.iconPoint()
- elif geom == QgsWkbTypes.LineGeometry:
- self.icon = QgsLayerItem.iconLine()
- elif geom == QgsWkbTypes.PolygonGeometry:
- self.icon = QgsLayerItem.iconPolygon()
- else:
- self.icon = QgsLayerItem.iconRaster()
- self.setIcon(0, self.icon)
-
- def createStaticSettings(self):
- self.dlg = LayerSettingsDialog()
- self.dlg.setEditState(False)
- self.dlg.pushButtonOk.clicked.connect(self.normalClose)
- self.dlg.editCheckBox.clicked.connect(self.startEditing)
- self.dlg.pushButtonCancel.clicked.connect(self.stopEditing)
- if self.datatype == "Vector" or self.datatype == "":
- fieldNames = self.ressource.getFieldNamesFromWfs(self.source["layerNames"])
- if fieldNames:
- self.dlg.hoverBox.addItems(fieldNames)
- else:
- self.dlg.deactivateHoverEditing()
-
- def populateSettings(self):
- settings = self.ressource.getLayerAttrsById(self.id)
- for attr in self.settings:
- self.settings[attr] = settings[attr]
-
- self.dlg.nameEdit.setText(settings["name"])
-
- opac = settings["appearance"]["opacity"]
- if opac is None:
- opac = 0
- self.dlg.sliderEdit.setText(str(opac))
- self.dlg.slider.setValue(int(opac * 100))
- if settings["appearance"]["hoverTemplate"] is not None:
- self.dlg.hoverEdit.setText(settings["appearance"]["hoverTemplate"])
-
- self.userPermissions = self.ressource.getObjectPermissions(
- self.id, "Layer", "User"
- )
- self.groupPermissions = self.ressource.getObjectPermissions(
- self.id, "Layer", "UserGroup"
- )
-
- if not self.userPermissions["success"] or not self.groupPermissions["success"]:
- self.dlg.noPermissionAccess()
- else:
- self.dlg.populateTable("users", self.userPermissions["data"]["permissions"])
- self.dlg.populateTable(
- "groups", self.groupPermissions["data"]["permissions"]
- )
-
- def getPermissionChanges(self, _type):
- if _type == "User":
- permissions = self.userPermissions["data"]
- table = self.dlg.usertabel
- elif _type == "UserGroup":
- permissions = self.groupPermissions["data"]
- table = self.dlg.groupstabel
- else:
- return
- oldPermissionList = permissions["permissions"]
- newPermissionList = []
- for entry in oldPermissionList:
- name = entry["displayTitle"]
- row = None
- for x in range(table.rowCount()):
- if table.verticalHeaderItem(x).text() == name:
- row = x
- break
- if row is None:
- return
-
- currentPermissionsInTable = []
- for p in enumerate(["READ", "UPDATE", "DELETE"]):
- if table.item(row, p[0]).checkState() == 2:
- currentPermissionsInTable.append(p[1])
-
- if entry["permissions"] is None:
- oldPermissions = []
- else:
- oldPermissions = entry["permissions"]["permissions"]
- if oldPermissions == ["ADMIN"]:
- oldPermissions = ["READ", "UPDATE", "DELETE"]
-
- if set(currentPermissionsInTable) != set(oldPermissions):
- newEntry = {
- "targetEntity": permissions["targetEntity"],
- "permissions": [
- {
- "permissions": {"permissions": currentPermissionsInTable},
- "targetEntity": entry["targetEntity"],
- }
- ],
- }
- newPermissionList.append(newEntry)
- if len(newPermissionList) == 0:
- newPermissionList = None
- return newPermissionList
-
- def getAllChanges(self):
- changes = {}
- generalChanges = self.getGeneralChanges()
- if generalChanges is not None:
- changes["general"] = generalChanges
- userPermissionChanges = self.getPermissionChanges("User")
- if userPermissionChanges is not None:
- changes["permissions"] = {"User": userPermissionChanges}
- groupPermissionChanges = self.getPermissionChanges("UserGroup")
- if groupPermissionChanges is not None:
- if "permissions" not in changes:
- changes["permissions"] = {}
- changes["permissions"]["UserGroup"] = groupPermissionChanges
-
- if len(changes) == 0:
- return None
- else:
- return changes
-
- def getGeneralChanges(self):
- changes = {}
- opacity = self.dlg.sliderEdit.text()
- if self.dlg.nameEdit.text() != self.settings["name"]:
- changes["name"] = self.dlg.nameEdit.text()
- if type(opacity) is float or type(opacity) is int:
- if float(opacity) != self.settings["appearance"]["opacity"]:
- changes["appearance"] = {}
- changes["appearance"]["opacity"] = float(opacity)
- hover_input = self.dlg.hoverEdit.text()
- if len(hover_input) == 0:
- hover_input = None
- if hover_input != self.settings["appearance"]["hoverTemplate"]:
- if "appearance" not in changes:
- changes["appearance"] = {}
- changes["appearance"]["hoverTemplate"] = self.dlg.hoverEdit.text()
-
- if len(changes) == 0:
- return None
- else:
- return changes
-
- def startEditing(self):
- self.dlg.setEditState(True)
- self.dlg.editCheckBox.clicked.disconnect(self.startEditing)
- self.dlg.pushButtonOk.clicked.disconnect(self.normalClose)
- self.dlg.editCheckBox.clicked.connect(self.stopEditing)
- self.dlg.pushButtonOk.clicked.connect(self.saveChanges)
-
- def stopEditing(self):
- changes = self.getAllChanges()
- if changes is not None:
- warn = QMessageBox.warning(
- self.dlg,
- "Warning",
- "All changes will be" " lost. Continue?",
- QMessageBox.Cancel,
- QMessageBox.Ok,
- )
- if warn == QMessageBox.Ok:
- self.populateSettings()
- else:
- self.dlg.editCheckBox.setChecked(True)
- return
- self.stoppedEditing()
-
- def saveChanges(self):
- changes = self.getAllChanges()
- if changes is not None:
- conf = QMessageBox.warning(
- self.dlg,
- "Confirm",
- "Please confirm you " "want to save the changes",
- QMessageBox.Cancel,
- QMessageBox.Ok,
- )
- if conf == QMessageBox.Ok:
- if "general" in changes:
- data = changes["general"]
- data["id"] = self.id
- responseCode = self.ressource.editLayer(self.id, data)
- if "name" in data and responseCode > 199 and responseCode < 299:
- self.name = data["name"]
- self.setText(0, self.name)
-
- # update the settings of the class:
- newSettings = self.ressource.updateSingleLayer(self.id)
- for attr in self.settings:
- self.settings[attr] = newSettings[attr]
- if "permissions" in changes:
- responses = 200
- for x in changes["permissions"].keys():
- for entry in changes["permissions"][x]:
- responseBool = self.ressource.editObjectPermission(
- self.id, "ProjectLayer", x, entry
- )
- if not responseBool:
- responses = 400
-
- # update the class variable copy of the permissions
- self.userPermissions = self.ressource.getObjectPermissions(
- self.id, "Layer", "User"
- )
- self.groupPermissions = self.ressource.getObjectPermissions(
- self.id, "Layer", "UserGroup"
- )
- self.ressource.userInfo(responses, "Layer permissions", "updated")
-
- # if the user clicked 'cancel' just return and stay in edit mode:
- else:
- return
- else:
- QMessageBox.information(
- self.dlg, "Note", "No changes were found", QMessageBox.Ok
- )
- self.stoppedEditing()
-
- def stoppedEditing(self):
- self.dlg.editCheckBox.clicked.disconnect(self.stopEditing)
- self.dlg.pushButtonOk.clicked.disconnect(self.saveChanges)
- self.dlg.pushButtonOk.clicked.connect(self.normalClose)
- self.dlg.editCheckBox.clicked.connect(self.startEditing)
- self.dlg.editCheckBox.setChecked(False)
- self.dlg.setEditState(False)
-
- def normalClose(self):
- self.dlg.hide()
-
- def deleteLayer(self):
- txt = (
- "Please confirm you want to permanently delete the selected layer in Shogun"
- )
- conf = QMessageBox.warning(
- self.dlg, "Confirm", txt, QMessageBox.Cancel, QMessageBox.Ok
- )
- if conf == QMessageBox.Ok:
- self.ressource.deleteLayer(self.id)
- self.parent().update()
-
-
-class QgisLayerItem(TreeItem):
- def __init__(self, qgislayer, shogunLayerItem, ressource):
- TreeItem.__init__(self, None, qgislayer.name())
- self.layer = qgislayer
- self.id = self.layer.id()
- self.type = self.layer.type()
- self.stylename = None
- # make upload and download Style only available for vector layers:
- if self.type == 0:
- self.actiontype = "qgisLayerReference"
- else:
- self.actiontype = None
- self.parentShogunLayer = shogunLayerItem
- self.ressource = ressource
-
- QgsProject.instance().addMapLayer(self.layer)
- if self.layer.type() == QgsMapLayer.VectorLayer:
- self.downloadStyle()
- self.layer.nameChanged.connect(self.on_name_changed)
-
- def uploadStyle(self):
- msg = "Please confirm that you want to upload the style of the selected"
- msg += " layer and overwrite the style of the corresponding layer in Shogun"
- confirm = QMessageBox.warning(
- None, "Confirm", msg, QMessageBox.Cancel, QMessageBox.Ok
- )
- if not confirm:
- return
- else:
- return self.ressource.uploadStyle(self)
-
- def downloadStyle(self):
- sld, geoServerStyleName = self.ressource.downloadStyle(self)
- self.stylename = geoServerStyleName
- if sld is not None:
- self.layer.loadSldStyle(sld)
- self.layer.triggerRepaint()
- return True
- else:
- return False
-
- def uploadIcon(self):
- self.ressource.uploadCustomIcon(self.layer.rendererV2().symbol().symbolLayer(0))
-
- def on_name_changed(self):
- self.setText(0, self.layer.name())
diff --git a/help/Makefile b/help/Makefile
deleted file mode 100644
index 9def777..0000000
--- a/help/Makefile
+++ /dev/null
@@ -1,130 +0,0 @@
-# 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/help/make.bat b/help/make.bat
deleted file mode 100644
index 3377610..0000000
--- a/help/make.bat
+++ /dev/null
@@ -1,155 +0,0 @@
-@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/help/source/conf.py b/help/source/conf.py
deleted file mode 100644
index 5ed7701..0000000
--- a/help/source/conf.py
+++ /dev/null
@@ -1,214 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# GeoportalRlpMetadataSearch 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.
-
-# 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'MDIDEMetadataSearch'
-copyrightTerrestris = u'2024, terrestris GmbH & Co. KG'
-
-# 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', 'GeoportalRlpMetadataSearch.tex', u'GeoportalRlpMetadataSearch Documentation',
- u'Armin Retterath', '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'GeoportalRlpMetadataSearch Documentation',
- [u'Armin Retterath'], 1)
-]
diff --git a/help/source/index.rst b/help/source/index.rst
deleted file mode 100644
index 6ef3039..0000000
--- a/help/source/index.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-.. GeoportalRlpMetadataSearch 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 GeoportalRlpMetadataSearch's documentation!
-============================================
-
-Contents:
-
-.. toctree::
- :maxdepth: 2
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
diff --git a/i18n/af.ts b/i18n/af.ts
deleted file mode 100644
index 615a88c..0000000
--- a/i18n/af.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
- @default
-
-
- Good morning
- Goeie more
-
-
-
diff --git a/i18n/gprlp_metadata_search.pro b/i18n/gprlp_metadata_search.pro
deleted file mode 100644
index 040346d..0000000
--- a/i18n/gprlp_metadata_search.pro
+++ /dev/null
@@ -1,3 +0,0 @@
-FORMS = ../gprlp_metadata_search_dialog_base.ui
-SOURCES = ../gprlp_metadata_search_dialog.py ../gprlp_metadata_search.py ../record/csw202record.py
-TRANSLATIONS = gprlp_metadata_search_de.ts
diff --git a/i18n/gprlp_metadata_search_de.qm b/i18n/gprlp_metadata_search_de.qm
deleted file mode 100644
index 25738d6..0000000
Binary files a/i18n/gprlp_metadata_search_de.qm and /dev/null differ
diff --git a/i18n/gprlp_metadata_search_de.ts b/i18n/gprlp_metadata_search_de.ts
deleted file mode 100644
index b06fd40..0000000
--- a/i18n/gprlp_metadata_search_de.ts
+++ /dev/null
@@ -1,444 +0,0 @@
-
-
-
-
- CSW202Record
-
-
- Online Metadata
- Online Metadaten
-
-
-
- Resource registration
- Datenquellen Registrierung
-
-
-
- Application
- Anwendung
-
-
-
- GeoportalRlpMetadataSearch
-
-
- &GeoPortal.rlp Metadata Search
- GeoPortal.rlp Metadatensuche
-
-
-
- GeoPortal.rlp metadata search
- GeoPortal.rlp Metadatensuche
-
-
-
- Resource registration
- Datenquellen Registrierung
-
-
-
- OWS Context
- OWS Context
-
-
-
- Map Layer
- Kartenebene
-
-
-
- Dataset
- Datensatz
-
-
-
- Remote CSW
- Entfernter CSW
-
-
-
- Rhineland-Palatinate
- Rheinland-Pfalz
-
-
-
- Hesse
- Hessen
-
-
-
- Saarland
- Saarland
-
-
-
- Context layer
- Context Ebene
-
-
-
- No preview
- Keine Vorschau
-
-
-
- Map layer
- Kartenebene
-
-
-
- Open in Browser
- In Browser öffnen
-
-
-
- Online Metadata
- Online Metadaten
-
-
-
- Searching...
- Suche...
-
-
-
- Ready
- Fertig
-
-
-
- Access options
- Zugriffsmöglichkeiten
-
-
-
- Download options
- Downloadoptionen
-
-
-
- View options
- Anzeigeoptionen
-
-
-
- WMS Layer
- WMS Ebene
-
-
-
- No organisation found
- Keine Organisation gefunden
-
-
-
- Preview...
- Vorschau...
-
-
-
- Found OWS Context documents
- Gefundene Context Dokumente
-
-
-
- Found map layers (WMS)
- Gefundene WMS Ebenen
-
-
-
- Found datasets
- Gefundene Datensätze
-
-
-
- Help
- Hilfe
-
-
-
- seconds
- Sekunden
-
-
-
- Search error: {0}
-
-
-
-
- Search error
- Suchfehler
-
-
-
- Error connecting to service: {0}
-
-
-
-
- CSW Connection error
- CSW Verbindungsfehler
-
-
-
- No results
- Keine Ergebnisse
-
-
-
- GeoportalRlpMetadataSearchDialogBase
-
-
- GeoPortal.rlp Metadata Search
- GeoPortal.rlp Metadatensuche
-
-
-
- Start Search
- Suchen
-
-
-
- SearchText
- Suchtext
-
-
-
- Results
- Ergebnisse
-
-
-
- Preview
- Vorschau
-
-
-
- Load
- Laden
-
-
-
- Unknown
- Unbekannt
-
-
-
- Page
- Seite
-
-
-
- 0
- 0
-
-
-
- from
- von
-
-
-
- <
- <
-
-
-
- >
- >
-
-
-
- Further information
- Weitere Infos
-
-
-
- Logo
- Logo
-
-
-
- Resource type:
- Typ:
-
-
-
- Resource id:
- ID:
-
-
-
- Load count:
- Nutzung:
-
-
-
- Organization:
- Organisation:
-
-
-
- Last modified:
- Geändert:
-
-
-
- Licence:
- Lizenz:
-
-
-
- Restrictions:
- Restriktionen:
-
-
-
- Access url:
- Zugriffspunkt:
-
-
-
- Metadata:
- Metadaten:
-
-
-
- Extent (EPSG:4326):
- Ausdehnung (EPSG:4326):
-
-
-
- Online Usage:
- Online Nutzung:
-
-
-
- Extent:
- Ausdehnung:
-
-
-
- MDI-DE Metadata Search
- MDI-DE Metadatensuche
-
-
-
- Filters
- Filter
-
-
-
- Type
- Typ
-
-
-
- Application
- Anwendung
-
-
-
- Series
- Serie
-
-
-
- Download
- Download
-
-
-
- Dataset
- Datensatz
-
-
-
- Map
- Karte
-
-
-
- Tile
- Kachel
-
-
-
- Region
- Region
-
-
-
- Baltic Sea
- Ostsee
-
-
-
- North Sea
- Nordsee
-
-
-
- MDI-DEMetadataSearch
-
-
- No preview
- Keine Vorschau
-
-
-
- csw202record
-
-
- No preview
- Keine Vorschau
-
-
-
- SW Lon / Lat
- SW Länge / Breite
-
-
-
- NE Lon / Lat
- NO Länge / Breite
-
-
-
- Extent of the Dataset
- Ausdehnung des Datensatzes
-
-
-
- Online Metadata
- Online Metadaten
-
-
-
- Result from MDI-DE Metadata search plugin
- Ergebnis vom MDI-DE Metadatensuche-Plugin
-
-
-
- Resource registration
- Datenquellen Registrierung
-
-
-
- %s Resource added to QGIS Browser under 'Result from MDI-DE Metadata search plugin'. You may open the Browser and add it to your Layers.
- %s Datenquelle wurde dem QGIS Browser unter 'Ergebnis vom MDI-DE Metadatensuche-Plugin' hinzugefügt. Sie können den Browser öffnen und die Datenquelle zu Ihren Layern hinzufügen.
-
-
-
- GetCapabilities
- GetCapabilities
-
-
-
- Application
- Anwendung
-
-
-
diff --git a/layerutils.py b/layerutils.py
deleted file mode 100644
index b66d02d..0000000
--- a/layerutils.py
+++ /dev/null
@@ -1,529 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-(c) 2025 terrestris GmbH & Co. KG, https://www.terrestris.de/en/
-+ the last method "getLabelingAsSld()" was taken
-+ from the qgis geoserver explorer plugin by
-+ (C) 2016 Boundless, http://boundlessgeo.com
-+ see: https://github.com/boundlessgeo/qgis-geoserver-plugin
-"""
-
-import os.path
-import sys
-import tempfile
-import urllib.parse
-import urllib.request
-import zipfile
-
-from qgis.core import (
- QgsWkbTypes,
- QgsMapLayer,
- QgsRasterFileWriter,
- QgsRasterLayer,
- QgsRasterPipe,
- QgsVectorFileWriter,
- QgsVectorLayer,
-)
-from qgis.PyQt.QtWidgets import QMessageBox
-from qgis.PyQt.QtXml import QDomDocument
-
-from .gui.dialog_bases.addraster import AddRasterDialog
-
-PYTHON_VERSION = sys.version_info[0]
-
-__author__ = "ntreff"
-__date__ = "July 2025"
-
-"""This module contains some helper functions for the shogun-editor plugin"""
-
-
-def createLayer(layerItem, epsg):
- # Shogun Webapp saves all layers in the following espg, therefore the
- # variable can be static
- layerurl = layerItem.source["url"]
-
- dataType = layerItem.datatype
-
- # every layerItem.source should have an attribute 'dataType'
- if dataType == "vector" or dataType == "Vector":
- url = layerItem.ressource.baseurl.rstrip("/shogun2-webapp/") + layerurl + "?"
- return createWfsLayer(layerItem, url, epsg)
-
- elif dataType == "Raster":
- url = layerItem.ressource.baseurl.rstrip("/shogun2-webapp/") + layerurl + "?"
- return createRasterLayer(layerItem, url, epsg)
-
- elif dataType == "WMS":
- if layerurl == "/shogun2-webapp/geoserver.action":
- url = (
- layerItem.ressource.baseurl.rstrip("/shogun2-webapp/") + layerurl + "?"
- )
- return createWmsLayerFromShogun(layerItem, url)
- else:
- return createWmsLayer(layerItem, layerurl, epsg) # TODO
-
- # if for any reason the parameter 'dataType' is not set correctly, we check the url
- # of the layer to determine if it's a WFS/WCS from the shogun-geoserver
- # (url has'shogun2-webapp') or if it's a WMS from an outer source (other url)
- elif dataType == "unknown" or dataType is None or dataType == "":
- if layerurl.startswith("/shogun2-webapp"):
- url = (
- layerItem.ressource.baseurl.rstrip("/shogun2-webapp/") + layerurl + "?"
- )
- try:
- lyr = createWfsLayer(layerItem, url, epsg)
- if lyr.isValid():
- return lyr
- except Exception as e:
- print('Could not create WFS layer: ' + str(e))
- pass
- try:
- lyr = createWmsLayerFromShogun(layerItem, url, epsg)
- if lyr.isValid():
- return lyr
- except Exception as e:
- print('Could not create WMS layer for SHOGun layer: ' + str(e))
- pass
- try:
- lyr = createRasterLayer(layerItem, url, epsg)
- if lyr.isValid():
- return lyr
- except Exception as e:
- print('Could not create layer: ' + str(e))
- pass
- else:
- return createWmsLayerNormal(layerItem, layerurl, epsg)
-
- else:
- info = "Layer source " + layerurl + " could not be loaded"
- QMessageBox.warning(None, "Warning", info, QMessageBox.Ok)
-
-
-def createWfsLayer(layerItem, url, epsg):
- params = {
- "service": "WFS",
- "srsname": epsg,
- "typename": layerItem.source["layerNames"],
- "typenames": layerItem.source["layerNames"],
- "url": url + "typename=" + layerItem.source["layerNames"],
- "version": "auto",
- "request": "GetCapabilities",
- }
- if PYTHON_VERSION >= 3:
- url = url + urllib.parse.unquote(urllib.parse.urlencode(params))
- else:
- url = url + urllib.unquote(urllib.urlencode(params))
-
- layer = QgsVectorLayer(url, "SHOGUN-Layer: " + layerItem.name, "WFS")
- return layer
-
-
-def createRasterLayer(layerItem, url, epsg):
-
- dlg = AddRasterDialog()
- dlg.show()
- userSelection = dlg.exec_()
-
- if userSelection == 0:
- return False
-
- elif userSelection == 1:
- return createWmsLayerFromShogun(layerItem, url, epsg)
-
- elif userSelection == 2:
-
- # workaround...
- # TODO: can this be made using the qgis wcs provider?
- layerName = layerItem.source["layerNames"]
-
- params = {
- "request": "GetCoverage",
- "version": "2.0.1",
- "coverageId": layerName,
- "namespace": "SHOGUN",
- "identifier": layerName,
- }
-
- # build the fitting url from the baseurl ('http...geoserver.action?')
- # and the parameters
- baseurl = url + "service=WCS&"
- if PYTHON_VERSION >= 3:
- url = baseurl + urllib.parse.urlencode(params)
- else:
- url = baseurl + urllib.urlencode(params)
-
- # urlretrieve stores the downloaded file in /tmp/ and returns it's path:
- if PYTHON_VERSION >= 3:
- response = urllib.request.urlretrieve(url)
- else:
- response = urllib.urlretrieve(url)
- file = response[0]
- # create a raster layer and return it
- return QgsRasterLayer(file, "QGIS-Layer: " + layerItem.name)
-
-
-def createWmsLayerNormal(layerItem, url, epsg):
- params = {
- "crs": epsg,
- "dpiMode": "7",
- "format": "image/png",
- "layers": layerItem.source["layerNames"],
- "styles": "",
- "url": url,
- }
-
- if PYTHON_VERSION >= 3:
- uri = urllib.parse.urlencode(params)
- else:
- uri = urllib.urlencode(params)
-
- layer = QgsRasterLayer(uri, "QGIS-Layer: " + layerItem.name, "wms")
- if layer.isValid():
- return layer
- else:
- return False
-
-
-def createWmsLayer(layerItem, url, epsg):
- # TODO
- return False
-
-
-def createWmsLayerFromShogun(layerItem, url, epsg):
- layerNames = layerItem.source["layerNames"]
- params = {
- "IgnoreGetMapUrl": 1,
- "crs": epsg,
- "format": "image/png",
- "layers": layerNames.split(":")[1],
- "styles": "",
- "url": url + "layers=" + layerNames,
- }
-
- if PYTHON_VERSION >= 3:
- uri = urllib.parse.urlencode(params)
- else:
- uri = urllib.urlencode(params)
- layer = QgsRasterLayer(uri, "QGIS-Layer: " + layerItem.name, "wms")
- if layer.isValid():
- return layer
- else:
- return False
-
-
-def createAndParseSld(qgisLayerItem):
- document = QDomDocument()
- header = document.createProcessingInstruction(
- "xml", "version='1.0' encoding='UTF-8'"
- )
- document.appendChild(header)
- root = document.createElementNS(
- "http://www.opengis.net/sld", "StyledLayerDescriptor"
- )
- root.setAttribute("version", "1.0.0")
- root.setAttribute("xmlns:ogc", "http://www.opengis.net/ogc")
- root.setAttribute("xmlns:sld", "http://www.opengis.net/sld")
- root.setAttribute("xmlns:gml", "http://www.opengis.net/gml")
- document.appendChild(root)
-
- namedLayerNode = document.createElement("sld:NamedLayer")
- root.appendChild(namedLayerNode)
-
- qgisLayerItem.layer.writeSld(namedLayerNode, document, "")
-
- nameNode = namedLayerNode.firstChildElement("se:Name")
- oldNameText = nameNode.firstChild()
- newname = qgisLayerItem.parentShogunLayer.source["layerNames"]
- newNameText = document.createTextNode(newname)
- nameNode.appendChild(newNameText)
- nameNode.removeChild(oldNameText)
-
- userStyleNode = namedLayerNode.firstChildElement("UserStyle")
- userStyleNameNode = userStyleNode.firstChildElement("se:Name")
- userStyleNameText = userStyleNameNode.firstChild()
- userStyleNameNode.removeChild(userStyleNameText)
- userStyleNameNode.appendChild(document.createTextNode(qgisLayerItem.stylename))
-
- titleNode = document.createElement("sld:Title")
- title = document.createTextNode("A QGIS-Style for " + qgisLayerItem.layer.name())
- titleNode.appendChild(title)
- userStyleNode.appendChild(titleNode)
- defaultNode = document.createElement("sld:IsDefault")
- defaultNode.appendChild(document.createTextNode("1"))
- userStyleNode.appendChild(defaultNode)
-
- featureTypeStyleNode = userStyleNode.firstChildElement("se:FeatureTypeStyle")
- featureTypeStyleNameNode = document.createElement("sld:Name")
- featureTypeStyleNameNode.appendChild(document.createTextNode("name"))
- featureTypeStyleNode.appendChild(featureTypeStyleNameNode)
-
- rules = featureTypeStyleNode.elementsByTagName("se:Rule")
- for x in range(rules.length()):
- rule = rules.at(x)
- rule.removeChild(rule.firstChildElement("se:Description"))
-
- # Check if custom icons are used in symbology and replace the text:
- # search if tag 'se:OnlineResource' is in the sld document
- listOfGraphics = rule.toElement().elementsByTagName("se:OnlineResource")
- if not listOfGraphics.isEmpty():
- for x in range(listOfGraphics.length()):
- graphicNode = listOfGraphics.at(x)
- currentIcon = graphicNode.attributes().namedItem("xlink:href").nodeValue()
- iconUrl = qgisLayerItem.ressource.prepareIconForUpload(currentIcon)
- graphicNode.toElement().setAttribute("xlink:href", iconUrl)
- graphicNode.toElement().setAttribute(
- "xmlns:xlink", "http://www.w3.org/1999/xlink"
- )
-
- sld = document.toString()
-
- # in qgis3 layer.writeSld() also includes labeling in the output sld,
- # whereas in qgis2 we have to do this manually by using this module's function
- # getLabelingAsSld
- # TODO: The automatic sld labeling from QGIS 3 produces an extra rule for
- # every labeling style, thus leading to a less beautiful viewe in the
- # shogun2-webapp styler - is this a problem?
-
- if qgisLayerItem.layer.labelsEnabled() and PYTHON_VERSION < 3:
- labelSld = getLabelingAsSld(qgisLayerItem.layer)
- sld = sld.replace("", labelSld + "")
-
- sld = sld.replace('ogc:Filter xmlns:ogc="http://www.opengis.net/ogc"', "ogc:Filter")
-
- # the following fixes weird problems with the sld compability with the
- # shogun webapp
- sld = sld.replace("", "")
- sld = sld.replace(" ", "")
- sld = sld.replace(" ", "")
- sld = sld.replace(" ", " ")
-
- sld = sld.replace("StyledLayerDescriptor", "sld:StyledLayerDescriptor")
- sld = sld.replace("UserStyle", "sld:UserStyle")
- sld = sld.replace("se:", "sld:")
- sld = sld.replace("SvgParameter", "CssParameter")
- sld = sld.replace("\n", "")
- sld = sld.replace("\t", "")
-
- return sld
-
-
-def prepareLayerForUpload(layer, uploadDialog):
- tmpdir = tempfile.mkdtemp()
- zipfilePath = os.path.join(tmpdir, "uploadzip.zip")
- uploadDialog.log("Writing layer as shapefile...")
-
- if layer.type() == QgsMapLayer.VectorLayer:
- if layer.source().endswith(".shp"):
- file = layer.source()
- uploadDialog.log("...success\nZipping...")
- zipSuccess = createZipFromShapefile(file, zipfilePath, delete=False)
- else:
- path = os.path.join(tmpdir, "VectorlayerFromQGisPlugin.shp")
- file = writeShapefile(layer, path)
- if not file:
- uploadDialog.log("Error: Could not write the shapefile ")
- return
- uploadDialog.log("...success\nZipping...")
- zipSuccess = createZipFromShapefile(file, zipfilePath, delete=True)
-
- else:
- if layer.providerType() == "wms":
- return True
- if layer.source().endswith(".tif"):
- rasterfile = layer.source()
- uploadDialog.log("Zipping...")
- zipSuccess = createZipFromRaster(rasterfile, zipfilePath, delete=False)
- else:
- path = os.path.join(tmpdir, "RasterlayerFromQGisPlugin.tif")
- uploadDialog.log("Creating raster file from layer...")
- rasterfile = writeRasterFile(layer, path)
- uploadDialog.log("Zipping...")
- if not rasterfile:
- uploadDialog.log("Error: Could not write the raster file")
- return
- zipSuccess = createZipFromRaster(rasterfile, zipfilePath, delete=True)
- uploadDialog.log(zipSuccess)
- return zipfilePath, tmpdir
-
-
-def writeShapefile(layer, path):
- # TODO: here are some problems with the upload - also shogun-problems
-
- writeError = QgsVectorFileWriter.writeAsVectorFormat(
- layer, path, "utf-8", layer.crs(), "ESRI Shapefile", False
- )
- if PYTHON_VERSION >= 3:
- if writeError[0] != 0: # in QGIS 3 writeAsVectorFormat returns a tuple
- return False
- else:
- if writeError != 0: # 0 = writing success
- return False
- return path
-
-
-def createZipFromShapefile(filepath, zipfilePath, delete=False):
- # create a list with '/path/name.shp' as the first item
- shapefileParts = [filepath]
- for extension in [".dbf", ".shx", ".prj"]:
- newFilename = os.path.splitext(filepath)[0] + extension
- if os.path.isfile(newFilename):
- # append the further shapefile parts to the list
- shapefileParts.append(newFilename)
- else:
- return "Error: Could not find " + os.path.basename(newFilename)
-
- # create a new zip-archive at zipfilePath
- newZipfile = zipfile.ZipFile(zipfilePath, "w", zipfile.ZIP_DEFLATED)
- for part in shapefileParts:
- # add the parts of the shapefile to the zip-archive
- newZipfile.write(part, arcname=os.path.basename(part))
- # then delete the parts because we do not need them anymore
- if delete:
- os.remove(part)
- newZipfile.close()
-
- # delete further created files belonging to the shapefile, because we
- # do not need them
- if delete:
- for extension in [".cpg", ".qpj"]:
- newFilename = os.path.splitext(filepath)[0] + extension
- if os.path.isfile(newFilename):
- os.remove(newFilename)
- return "Written successfully"
-
-
-def writeRasterFile(layer, filepath):
- pipe = QgsRasterPipe()
- provider = layer.dataProvider()
- pipe.set(provider.clone())
-
- writer = QgsRasterFileWriter(filepath)
- writeError = writer.writeRaster(
- pipe, provider.xSize(), provider.ySize(), provider.extent(), provider.crs()
- )
- print(writeError)
-
- return filepath
-
-
-def createZipFromRaster(pathToRaster, zipfilePath, delete=False):
- newZipfile = zipfile.ZipFile(zipfilePath, "w", zipfile.ZIP_DEFLATED)
- newZipfile.write(pathToRaster, arcname=os.path.basename(pathToRaster))
- newZipfile.close()
- if delete:
- try:
- os.remove(pathToRaster)
- except Exception as e:
- print('Could not remove:' + str(e))
- pass
- return "Written successfully"
-
-
-# the following function (we need it only when the plugin is used on qgis2):
-# (c) 2016 Boundless, http://boundlessgeo.com
-# This code is licensed under the GPL 2.0 license.
-def getLabelingAsSld(layer):
- try:
- s = ""
- s += (
- ""
- + layer.customProperty("labeling/fieldName")
- + " "
- )
- s += " "
- r = int(layer.customProperty("labeling/textColorR"))
- g = int(layer.customProperty("labeling/textColorG"))
- b = int(layer.customProperty("labeling/textColorB"))
- rgb = "#%02x%02x%02x" % (r, g, b)
- s += (
- ''
- + rgb
- + " "
- )
- s += ""
- s += (
- ''
- + layer.customProperty("labeling/fontFamily")
- + " "
- )
- s += (
- ''
- + str(int(layer.customProperty("labeling/fontSize")))
- + " "
- )
-
- italic = False
- bold = False
- if layer.customProperty("labeling/fontItalic") == "true":
- s += 'italic '
- italic = True
- if layer.customProperty("labeling/fontBold") == "true":
- bold = True
- s += 'bold'
- if not italic and not bold:
- s += 'normal '
- s += 'normal '
- s += " "
- s += ""
- if layer.geometryType() == QgsWkbTypes.PointGeometry:
- s += (
- ""
- ""
- "0.5 "
- "0.5 "
- " "
- )
- s += ""
- s += (
- ""
- + str(int(layer.customProperty("labeling/xOffset")))
- + " "
- )
- s += (
- ""
- + str(int(layer.customProperty("labeling/yOffset")))
- + " "
- )
- s += " "
- s += (
- ""
- + str(abs(int(layer.customProperty("labeling/angleOffset"))))
- + " "
- )
- s += " "
- if layer.geometryType() == QgsWkbTypes.LineGeometry:
- mode = layer.customProperty("labeling/placement")
- if mode != 4:
- follow = (
- 'true '
- if mode == 3
- else ""
- )
- s += """
-
- %s
-
-
- %s""" % (
- str(layer.customProperty("labeling/dist")),
- follow,
- )
- s += " "
-
- if layer.customProperty("labeling/bufferDraw") == "true":
- r = int(layer.customProperty("labeling/bufferColorR"))
- g = int(layer.customProperty("labeling/bufferColorG"))
- b = int(layer.customProperty("labeling/bufferColorB"))
- rgb = "#%02x%02x%02x" % (r, g, b)
- haloSize = str(layer.customProperty("labeling/bufferSize"))
- opacity = str(float(layer.customProperty("labeling/bufferColorA")) / 255.0)
- s += "%s " % haloSize
- s += '%s ' % rgb
- s += (
- '%s '
- % opacity
- )
- s += " "
- return s
- except Exception as e:
- print('Could not create SLD: ' + str(e))
- pass
diff --git a/Makefile b/plugin_code/Makefile
similarity index 99%
rename from Makefile
rename to plugin_code/Makefile
index bc56b92..637de3a 100644
--- a/Makefile
+++ b/plugin_code/Makefile
@@ -64,7 +64,7 @@ PEP8EXCLUDE=pydev,resources.py,conf.py,third_party,ui
# * Windows:
# AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins'
-QGISDIR=/home/armin/.local/share/QGIS/QGIS3/profiles/default/python/plugins/
+QGISDIR=/home/user/.local/share/QGIS/QGIS3/profiles/default/python/plugins/
#################################################
# Normally you would not need to edit below here
diff --git a/__init__.py b/plugin_code/__init__.py
similarity index 97%
rename from __init__.py
rename to plugin_code/__init__.py
index d3a6e93..0bd9fb1 100644
--- a/__init__.py
+++ b/plugin_code/__init__.py
@@ -3,7 +3,6 @@
/***************************************************************************
QgisShogunEditor
A QGIS plugin
- GeoPortal.rlp metadata search module
Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
-------------------
begin : 2025-09
diff --git a/metadata.txt b/plugin_code/metadata.txt
similarity index 100%
rename from metadata.txt
rename to plugin_code/metadata.txt
diff --git a/pb_tool.cfg b/plugin_code/pb_tool.cfg
similarity index 93%
rename from pb_tool.cfg
rename to plugin_code/pb_tool.cfg
index fbb3ad2..28dc607 100644
--- a/pb_tool.cfg
+++ b/plugin_code/pb_tool.cfg
@@ -1,12 +1,12 @@
#/***************************************************************************
-# GeoportalRlpMetadataSearch
+# QgisShogunEditor
#
# Configuration file for plugin builder tool (pb_tool)
# Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
# -------------------
-# begin : 2022-02-09
-# copyright : (C) 2022 by Armin Retterath
-# email : armin.retterath@gmail.com
+# begin : 2025-09
+# copyright : (C) 2025 by terrestris
+# email : info@terrestris.de
# ***************************************************************************/
#
#/***************************************************************************
diff --git a/plugin_upload.py b/plugin_code/plugin_upload.py
similarity index 98%
rename from plugin_upload.py
rename to plugin_code/plugin_upload.py
index 83c051c..446f207 100644
--- a/plugin_upload.py
+++ b/plugin_code/plugin_upload.py
@@ -10,7 +10,7 @@
import xmlrpc.client
from optparse import OptionParser
-standard_library.install_aliases()
+standard_library.install_aliases() # type: ignore
# Configuration
PROTOCOL = 'https'
diff --git a/pylintrc b/plugin_code/pylintrc
similarity index 99%
rename from pylintrc
rename to plugin_code/pylintrc
index 7e168f6..ca29cca 100644
--- a/pylintrc
+++ b/plugin_code/pylintrc
@@ -171,7 +171,7 @@ additional-builtins=
[FORMAT]
# Maximum number of characters on a single line.
-max-line-length=80
+max-line-length=120
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )??$
diff --git a/qgis_shogun_editor.py b/plugin_code/qgis_shogun_editor.py
similarity index 56%
rename from qgis_shogun_editor.py
rename to plugin_code/qgis_shogun_editor.py
index 842b2e1..d9834db 100644
--- a/qgis_shogun_editor.py
+++ b/plugin_code/qgis_shogun_editor.py
@@ -1,15 +1,14 @@
# -*- coding: utf-8 -*-
"""
/***************************************************************************
- ShogunQgisConfigurator
+ QgisShogunEditor
A QGIS plugin
- GeoPortal.rlp metadata search module
Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
-------------------
- begin : 2022-02-09
+ begin : 2025-09
git sha : $Format:%H$
- copyright : (C) 2022 by Armin Retterath
- email : armin.retterath@gmail.com
+ copyright : (C) 2025 by terrestris
+ email : info@terrestris.de
***************************************************************************/
/***************************************************************************
@@ -21,6 +20,7 @@
* *
***************************************************************************/
"""
+import json
import os.path
from qgis.core import (
@@ -28,20 +28,14 @@
QgsBrowserModel,
QgsMessageLog,
QgsNetworkAccessManager,
- QgsProject,
- QgsProviderRegistry,
- QgsRasterLayer,
QgsSettings,
)
-from qgis.gui import QgsGui, QgsMessageBar, QgsMessageViewer
# some things for doing http requests
-from qgis.PyQt.QtCore import QCoreApplication, QEventLoop, QSettings, Qt, QTranslator, QUrl, QUrlQuery
+from qgis.PyQt.QtCore import QCoreApplication, QEventLoop, QSettings, Qt, QTranslator, QUrl
from qgis.PyQt.QtGui import QDesktopServices, QIcon, QPixmap
-from qgis.PyQt.QtNetwork import QNetworkRequest
-from qgis.PyQt.QtWidgets import QAction, QMessageBox
-
-from .gui.editor import Editor
+from qgis.PyQt.QtNetwork import QNetworkRequest, QSslSocket
+from qgis.PyQt.QtWidgets import QAction
# Import the code for the dialog
from .qgis_shogun_editor_dialog import QgisShogunEditorDialog
@@ -64,11 +58,12 @@ def __init__(self, iface):
self.iface = iface
# initialize plugin directory
self.plugin_dir = os.path.dirname(__file__)
- # initialize locale
- locale = QSettings().value("locale/userLocale")[0:2]
+ # TODO initialize locale
+ locale = QSettings().value('locale/userLocale')[0:2]
locale_path = os.path.join(
- self.plugin_dir, "i18n", "gprlp_metadata_search_{}.qm".format(locale)
- )
+ self.plugin_dir,
+ 'i18n',
+ 'qgis_shogun_editor{}.qm'.format(locale))
if os.path.exists(locale_path):
self.translator = QTranslator()
@@ -77,7 +72,7 @@ def __init__(self, iface):
# Declare instance attributes
self.actions = []
- self.menu = self.tr("&Qgis Shogun Editor")
+ self.menu = self.tr(u'&Shogun Qgis Configurator')
# Check if plugin was started the first time in current QGIS session
# Must be set in initGui() to survive plugin reloads
@@ -91,12 +86,14 @@ def __init__(self, iface):
# network access
self.na_manager = QgsNetworkAccessManager.instance()
+ self.request = QNetworkRequest()
+ # Achtung: T/F bei Zertifikaten
self.disable_ssl_verification = self.settings.value(
- "/MetaSearch/disableSSL", False, bool
+ "/MetaSearch/disableSSL",
+ True,
+ bool
)
- self.pluginIsActive = False
- self.editor = None
# noinspection PyMethodMayBeStatic
def tr(self, message):
@@ -111,20 +108,19 @@ def tr(self, message):
:rtype: QString
"""
# noinspection PyTypeChecker,PyArgumentList,PyCallByClass
- return QCoreApplication.translate("QgisShogunEditor", message)
+ return QCoreApplication.translate('ShogunQgisConfigurator', 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,
- ):
+ 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
@@ -180,7 +176,9 @@ def add_action(
self.iface.addToolBarIcon(action)
if add_to_menu:
- self.iface.addPluginToWebMenu(self.menu, action)
+ self.iface.addPluginToWebMenu(
+ self.menu,
+ action)
self.actions.append(action)
@@ -191,18 +189,19 @@ def initGui(self):
icon_path = os.path.join(os.path.dirname(__file__), "shogun_logo.png")
self.add_action(
icon_path,
- text=self.tr("Qgis Shogun Editor"),
- callback=self.openEditor, # ehemalig: self.run
- parent=self.iface.mainWindow(),
- )
+ text=self.tr(u'Shogun Qgis Configurator'),
+ callback=self.run,
+ parent=self.iface.mainWindow())
# will be set False in run()
- # self.first_start = True
+ self.first_start = True
def unload(self):
"""Removes the plugin menu item and icon from QGIS GUI."""
for action in self.actions:
- self.iface.removePluginWebMenu(self.tr("Qgis Shogun Editor"), action)
+ self.iface.removePluginWebMenu(
+ self.tr(u'Shogun Qgis Configurator'),
+ action)
self.iface.removeToolBarIcon(action)
def run(self):
@@ -213,32 +212,25 @@ def run(self):
if self.first_start:
self.first_start = False
self.dlg = QgisShogunEditorDialog()
- # search_catalogues
- # important - events should only be added once - otherwise we will go into trouble!
- self.dlg.pushButton.clicked.connect(lambda: self.start_search())
+
+ self.dlg.entryUrl.setPlaceholderText('Please enter an URL')
+
+ self.dlg.loadButton.clicked.connect(lambda: self.load_applications())
+
# add logo
logo_path = os.path.join(os.path.dirname(__file__), "shogun_logo.png")
if logo_path:
# build
pixmap = QPixmap(logo_path)
# draw preview
- self.dlg.labelLogo.setPixmap(
- pixmap.scaled(
- self.dlg.labelLogo.size(),
- Qt.KeepAspectRatio,
- Qt.SmoothTransformation,
- )
- )
+ self.dlg.labelLogo.setPixmap(pixmap.scaled(self.dlg.labelLogo.size(),
+ Qt.KeepAspectRatio, Qt.SmoothTransformation))
self.dlg.labelLogo.mousePressEvent = self.open_project_link
else:
- QgsMessageLog.logMessage(
- "An error occured while try to open url: ",
- "GeoPortal.rlp search",
- level=Qgis.Critical,
- )
+ QgsMessageLog.logMessage("An error occured while try to open url: ", 'QgisShogunEditor',
+ level=Qgis.Critical)
# add link to github for help
# help_icon_path = os.path.join(os.path.dirname(__file__), "questionmark.png")
- # self.dlg.labelHelp.setOpenExternalLinks(True)
# show the dialog
self.dlg.show()
# Run the dialog event loop
@@ -253,24 +245,92 @@ def open_project_link(self, event):
if event.button() == Qt.LeftButton:
QDesktopServices.openUrl(QUrl("https://www.terrestris.de/de/"))
- def onClosePlugin(self):
- if self.editor is None:
+ def check_url_for_applications(self, input_url):
+ # check url and prepare for applications request
+ # TODO: check for http and /
+ input_url = str(input_url)
+ if len(input_url) == 0:
+ print('Please enter a valid url.')
return
- connections_nr = self.editor.topitem.childCount()
- for x in range(connections_nr):
- try:
- connection = self.editor.topitem.child(x)
- connection.disconnectSignals()
- except Exception as e:
- print('could not disconnect signals for connection' + str(e))
-
- def openEditor(self):
- if not self.pluginIsActive:
- self.pluginIsActive = True
-
- self.editor = Editor(self.iface)
- self.iface.addDockWidget(Qt.RightDockWidgetArea, self.editor.dock)
- self.editor.dock.show()
-
else:
- self.editor.dock.show()
+ if input_url.endswith('applications'):
+ return input_url
+ elif input_url.endswith('application'):
+ return input_url + 's'
+ elif input_url.endswith('/'):
+ return input_url + 'applications'
+
+ def check_url_for_layers(self, input_url):
+ # check url and prepare for layers request
+ # TODO: check for http and /
+ input_url = str(input_url)
+ if len(input_url) == 0:
+ print('Please enter a valid url.')
+ return
+ else:
+ if input_url.endswith('layers'):
+ return input_url
+ elif input_url.endswith('layer'):
+ return input_url + 's'
+ elif input_url.endswith('/'):
+ return input_url + 'layers'
+
+ def request_public_entity(self, url):
+ self.request.setUrl(QUrl(url))
+
+ # no certificate
+ ssl_config = self.request.sslConfiguration()
+ ssl_config.setPeerVerifyMode(QSslSocket.VerifyNone)
+ self.request.setSslConfiguration(ssl_config)
+
+ # request public application
+ self.reply = self.na_manager.get(self.request)
+ eventLoop = QEventLoop()
+ self.reply.finished.connect(eventLoop.quit)
+ eventLoop.exec_() # blocs until finished
+
+ if self.reply.error() == self.reply.NoError:
+ self.response = self.reply.readAll().data().decode("utf-8")
+ # print("Response:", self.response)
+ else:
+ self.response = None
+ print("Error:", self.reply.errorString())
+ self.reply.deleteLater()
+
+ return self.response
+
+ def find_all_layer_ids(self, layer_tree_json):
+ layer_ids = []
+
+ if "layerId" in layer_tree_json:
+ print('Found layerId', layer_tree_json["layerId"])
+ layer_ids.append(layer_tree_json["layerId"])
+
+ if "children" in layer_tree_json:
+ for child in layer_tree_json["children"]:
+ layer_ids.extend(self.find_all_layer_ids(child))
+
+ return layer_ids
+
+ def load_applications(self):
+ inputUrl = self.dlg.entryUrl.text()
+ # check url
+ applications_url = self.check_url_for_applications(inputUrl)
+ layers_url = self.check_url_for_layers(inputUrl)
+ if (len(applications_url) > 0 and len(layers_url) > 0):
+ # request (all public) applications
+ applications_response = self.request_public_entity(applications_url)
+ applications_json = json.loads(applications_response)
+ applicatons_content = applications_json['content'][0]
+ applications_layertree = applicatons_content['layerTree']
+ print('Applications', applications_json)
+ self.layer_ids = self.find_all_layer_ids(applications_layertree)
+
+ # request (all public) layers
+ layers_response = self.request_public_entity(layers_url)
+ layers_josn = json.loads(layers_response)
+ layers_content = layers_josn['content']
+
+ # get specific layers
+ result = [layer for layer in layers_content if layer["id"] == self.layer_ids[0]]
+ print('Layer', result)
diff --git a/qgis_shogun_editor_dialog.py b/plugin_code/qgis_shogun_editor_dialog.py
similarity index 98%
rename from qgis_shogun_editor_dialog.py
rename to plugin_code/qgis_shogun_editor_dialog.py
index 93eeabf..84ab0b4 100644
--- a/qgis_shogun_editor_dialog.py
+++ b/plugin_code/qgis_shogun_editor_dialog.py
@@ -3,7 +3,6 @@
/***************************************************************************
QgisShogunEditorDialog
A QGIS plugin
- GeoPortal.rlp metadata search module
Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
-------------------
begin : 2025-09
diff --git a/qgis_shogun_editor_dialog_base.ui b/plugin_code/qgis_shogun_editor_dialog_base.ui
similarity index 74%
rename from qgis_shogun_editor_dialog_base.ui
rename to plugin_code/qgis_shogun_editor_dialog_base.ui
index 0f15c62..103ad3c 100644
--- a/qgis_shogun_editor_dialog_base.ui
+++ b/plugin_code/qgis_shogun_editor_dialog_base.ui
@@ -11,9 +11,9 @@
- Qgis-Shogun-Editor
+ Shogun-Qgis-Configurator
-
+
780
@@ -67,6 +67,28 @@
+
+
+ true
+
+
+
+ 20
+ 40
+ 281
+ 28
+
+
+
+
+
+
+
+
+
+ false
+
+
diff --git a/requirements/development.txt b/plugin_code/requirements/development.txt
similarity index 100%
rename from requirements/development.txt
rename to plugin_code/requirements/development.txt
diff --git a/resource_column.py b/plugin_code/resource_column.py
similarity index 100%
rename from resource_column.py
rename to plugin_code/resource_column.py
diff --git a/resources.py b/plugin_code/resources.py
similarity index 100%
rename from resources.py
rename to plugin_code/resources.py
diff --git a/resources.qrc b/plugin_code/resources.qrc
similarity index 100%
rename from resources.qrc
rename to plugin_code/resources.qrc
diff --git a/plugin_code/shogun_logo.png b/plugin_code/shogun_logo.png
new file mode 100644
index 0000000..b46fed1
Binary files /dev/null and b/plugin_code/shogun_logo.png differ
diff --git a/shogun_logo.png b/plugin_code/shogun_logo_old.png
similarity index 100%
rename from shogun_logo.png
rename to plugin_code/shogun_logo_old.png
diff --git a/questionmark.png b/questionmark.png
deleted file mode 100644
index 8a9eb1a..0000000
Binary files a/questionmark.png and /dev/null differ
diff --git a/scripts/compile-strings.sh b/scripts/compile-strings.sh
deleted file mode 100644
index 9d76083..0000000
--- a/scripts/compile-strings.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/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/scripts/run-env-linux.sh b/scripts/run-env-linux.sh
deleted file mode 100644
index 668247c..0000000
--- a/scripts/run-env-linux.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/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/scripts/update-strings.sh b/scripts/update-strings.sh
deleted file mode 100644
index a31f712..0000000
--- a/scripts/update-strings.sh
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/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/setup.sh b/setup.sh
new file mode 100755
index 0000000..53e4ea3
--- /dev/null
+++ b/setup.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+xhost +
+
+function resetxhost {
+ echo "Resetting xhost"
+ xhost -
+}
+
+trap resetxhost EXIT
+docker compose -f docker/docker-compose-dev.yml up
+
+#--force-recreate
diff --git a/startDocker.sh b/startDocker.sh
deleted file mode 100755
index 67c9ce0..0000000
--- a/startDocker.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-
-xhost +local:docker
-docker run --rm -it --name qgis --net host \
- -e DISPLAY=$DISPLAY \
- -v /tmp/.X11-unix:/tmp/.X11-unix \
- -v ./:/root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/shogun_qgis_editor \
- qgis/qgis:3.44 \
- qgis