init plugin

This commit is contained in:
2026-03-19 14:06:08 +01:00
commit 5974778b0a
65 changed files with 6606 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
#! python3 # noqa: E265
"""
Metadata about the package to easily retrieve informations about it.
See: https://packaging.python.org/guides/single-sourcing-package-version/
"""
# ############################################################################
# ########## Libraries #############
# ##################################
# standard library
from configparser import ConfigParser
from datetime import date
from pathlib import Path
import json
# ############################################################################
# ########## Globals ###############
# ##################################
__all__: list = [
"__author__",
"__copyright__",
"__email__",
"__license__",
"__summary__",
"__title__",
"__uri__",
"__version__",
]
DIR_PLUGIN_ROOT: Path = Path(__file__).parent
PLG_METADATA_FILE: Path = DIR_PLUGIN_ROOT.resolve() / "metadata.txt"
# ############################################################################
# ########## Functions #############
# ##################################
def plugin_metadata_as_dict() -> dict:
"""Read plugin metadata.txt and returns it as a Python dict.
Raises:
IOError: if metadata.txt is not found
Returns:
dict: dict of dicts.
"""
config = ConfigParser(interpolation=None)
if PLG_METADATA_FILE.is_file():
config.read(PLG_METADATA_FILE.resolve(), encoding="UTF-8")
return {s: dict(config.items(s)) for s in config.sections()}
else:
raise IOError("Plugin metadata.txt not found at: %s" % PLG_METADATA_FILE)
# ############################################################################
# ########## Variables #############
# ##################################
# store full metadata.txt as dict into a var
__plugin_md__: dict = plugin_metadata_as_dict()
__author__: str = __plugin_md__.get("general").get("author")
__copyright__: str = "2026 - {0}, {1}".format(
date.today().year, __author__
)
__email__: str = __plugin_md__.get("general").get("email")
__icon_path__: Path = DIR_PLUGIN_ROOT.resolve() / __plugin_md__.get("general").get(
"icon"
)
__keywords__: list = [
t.strip() for t in __plugin_md__.get("general").get("repository").split("tags")
]
__license__: str = "GPLv3"
__plugin_dependencies__ = [
dep.strip()
for dep in __plugin_md__.get("general").get("plugin_dependencies", "").split(",")
if dep.strip()
]
__summary__: str = "{}\n{}".format(
__plugin_md__.get("general").get("description"),
__plugin_md__.get("general").get("about"),
)
__title__: str = __plugin_md__.get("general").get("name")
__title_clean__: str = "".join(e for e in __title__ if e.isalnum())
__uri_homepage__: str = __plugin_md__.get("general").get("homepage")
__uri_repository__: str = __plugin_md__.get("general").get("repository")
__uri_tracker__: str = __plugin_md__.get("general").get("tracker")
__uri__: str = __uri_repository__
__version__: str = __plugin_md__.get("general").get("version")
__version_info__: tuple = tuple(
[
int(num) if num.isdigit() else num
for num in __version__.replace("-", ".", 1).split(".")
]
)
__domain_url__: str = __plugin_md__.get("service").get("domain_name")
__biodiv_aura__: str = __plugin_md__.get("service").get("biodiv_aura")
__gbif__: str = __plugin_md__.get("service").get("gbif")
__api_sn_web__: str = __plugin_md__.get("export").get("synth_web")
__api_sn_exp__: str = __plugin_md__.get("export").get("synth_exp")
__api_lst_ex__: str = __plugin_md__.get("export").get("lstexport")
__api_cd_sta__: str = __plugin_md__.get("export").get("cd_status")
__login_url__: str = __plugin_md__.get("api").get("login_api")
__orb_tax_url__: str = __plugin_md__.get("ref_taxon").get("biodiv_aura")
__exept_export__: list = json.loads(__plugin_md__.get("export").get("except_export_term"))
# #############################################################################
# ##### Main #######################
# ##################################
if __name__ == "__main__":
plugin_md = plugin_metadata_as_dict()
assert isinstance(plugin_md, dict)
assert plugin_md.get("general").get("name") == __title__
print(f"Plugin: {__title__}")
print(f"By: {__author__}")
print(f"Version: {__version__}")
print(f"Description: {__summary__}")
print(f"Icon: {__icon_path__}")
print(
"For: %s > QGIS > %s"
% (
plugin_md.get("general").get("qgisminimumversion"),
plugin_md.get("general").get("qgismaximumversion"),
)
)
print(__title_clean__)
print(f"Depends on other plugins: {__plugin_dependencies__}")
+23
View File
@@ -0,0 +1,23 @@
#! python3 # noqa: E265
# ----------------------------------------------------------
# Copyright (C) 2015 Martin Dobias
# ----------------------------------------------------------
# Licensed under the terms of GNU GPL 2
#
# 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.
# --------------------------------------------------------------------
def classFactory(iface):
"""Load the plugin class.
:param iface: A QGIS interface instance.
:type iface: QgsInterface
"""
from .plugin_main import GnToolsPlugin
return GnToolsPlugin(iface)
View File
+45
View File
@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
"""
/***************************************************************************
gnToolsSynthese
A QGIS plugin
dlg_gn_tools
Plugin settings form integrated into QGIS 'Options' menu.
-------------------
begin : 2022-08-22
git sha : $Format:%H$
copyright : (C) 2022 by colas
email : colas.geier@cen-isere.org
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
import os
from qgis.PyQt import uic
from qgis.PyQt import QtWidgets
from datetime import datetime as dt
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(os.path.join(
os.path.dirname(__file__), 'dlg_gn_synthese.ui'))
class gnToolsSynthese(QtWidgets.QDialog, FORM_CLASS):
def __init__(self, parent=None):
"""Constructor."""
super(gnToolsSynthese, self).__init__(parent)
# Set up the user interface from Designer through FORM_CLASS.
# After self.setupUi() you can access any designer object by doing
# self.<objectname>, and you can use autoconnect slots - see
# http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
# #widgets-and-dialogs-with-auto-connect
self.setupUi(self)
+209
View File
@@ -0,0 +1,209 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>341</height>
</rect>
</property>
<property name="windowTitle">
<string>Paramètres de la Synthèse</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>50</x>
<y>290</y>
<width>341</width>
<height>32</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QgsCheckableComboBox" name="jdd_box">
<property name="geometry">
<rect>
<x>149</x>
<y>120</y>
<width>221</width>
<height>27</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="lab_jdd">
<property name="geometry">
<rect>
<x>20</x>
<y>118</y>
<width>121</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Jeu de données</string>
</property>
</widget>
<widget class="QgsCheckableComboBox" name="regne">
<property name="geometry">
<rect>
<x>150</x>
<y>160</y>
<width>221</width>
<height>27</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="lab_regne">
<property name="geometry">
<rect>
<x>20</x>
<y>160</y>
<width>131</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Règne de taxons</string>
</property>
</widget>
<widget class="QLabel" name="lab_dmin">
<property name="geometry">
<rect>
<x>20</x>
<y>200</y>
<width>121</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Date min</string>
</property>
</widget>
<widget class="QLabel" name="lab_dmax">
<property name="geometry">
<rect>
<x>20</x>
<y>240</y>
<width>121</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Date max</string>
</property>
</widget>
<widget class="QgsDateTimeEdit" name="date_min">
<property name="geometry">
<rect>
<x>150</x>
<y>200</y>
<width>221</width>
<height>28</height>
</rect>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
</widget>
<widget class="QgsDateTimeEdit" name="date_max">
<property name="geometry">
<rect>
<x>150</x>
<y>240</y>
<width>221</width>
<height>28</height>
</rect>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="send_to_gn">
<property name="geometry">
<rect>
<x>20</x>
<y>10</y>
<width>351</width>
<height>91</height>
</rect>
</property>
<property name="text">
<string>Pour plus de paramètres, accèder à la plateforme en ligne : </string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>QgsCheckableComboBox</class>
<extends>QComboBox</extends>
<header>qgscheckablecombobox.h</header>
</customwidget>
<customwidget>
<class>QgsDateTimeEdit</class>
<extends>QDateTimeEdit</extends>
<header>qgsdatetimeedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+45
View File
@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
"""
/***************************************************************************
gnToolsDialog
A QGIS plugin
dlg_gn_tools
Plugin settings form integrated into QGIS 'Options' menu.
-------------------
begin : 2022-08-22
git sha : $Format:%H$
copyright : (C) 2022 by colas
email : colas.geier@cen-isere.org
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
import os
from qgis.PyQt import uic
from qgis.PyQt import QtWidgets
from datetime import datetime as dt
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(os.path.join(
os.path.dirname(__file__), 'dlg_gn_tools.ui'))
class gnToolsDialog(QtWidgets.QDialog, FORM_CLASS):
def __init__(self, parent=None):
"""Constructor."""
super(gnToolsDialog, self).__init__(parent)
# Set up the user interface from Designer through FORM_CLASS.
# After self.setupUi() you can access any designer object by doing
# self.<objectname>, and you can use autoconnect slots - see
# http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
# #widgets-and-dialogs-with-auto-connect
self.setupUi(self)
+443
View File
@@ -0,0 +1,443 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>771</width>
<height>453</height>
</rect>
</property>
<property name="windowTitle">
<string>GéoNature tools</string>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>410</x>
<y>410</y>
<width>341</width>
<height>32</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QTabWidget" name="tab">
<property name="geometry">
<rect>
<x>70</x>
<y>110</y>
<width>631</width>
<height>281</height>
</rect>
</property>
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="tabletTracking">
<bool>true</bool>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="accessibleName">
<string>auth_url</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="import_2">
<attribute name="title">
<string>Import</string>
</attribute>
<widget class="QLabel" name="lab_src_imp">
<property name="geometry">
<rect>
<x>40</x>
<y>30</y>
<width>66</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Source : </string>
</property>
</widget>
<widget class="QComboBox" name="source_import">
<property name="geometry">
<rect>
<x>130</x>
<y>30</y>
<width>241</width>
<height>27</height>
</rect>
</property>
<property name="currentText">
<string/>
</property>
</widget>
<widget class="QPushButton" name="draw_bounds">
<property name="geometry">
<rect>
<x>350</x>
<y>90</y>
<width>251</width>
<height>27</height>
</rect>
</property>
<property name="text">
<string>Tracer une emprise</string>
</property>
</widget>
<widget class="QPushButton" name="active_filter_layer">
<property name="geometry">
<rect>
<x>40</x>
<y>90</y>
<width>231</width>
<height>27</height>
</rect>
</property>
<property name="text">
<string>Filtrer à partir d'une couche</string>
</property>
</widget>
<widget class="QgsMapLayerComboBox" name="filter_layer">
<property name="geometry">
<rect>
<x>40</x>
<y>150</y>
<width>251</width>
<height>27</height>
</rect>
</property>
<property name="accessibleName">
<string>Couche taxonomique</string>
</property>
<property name="allowEmptyLayer">
<bool>true</bool>
</property>
</widget>
<widget class="QCheckBox" name="use_entity">
<property name="geometry">
<rect>
<x>40</x>
<y>200</y>
<width>271</width>
<height>31</height>
</rect>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Utilisation de l'emprise</string>
</property>
<property name="text">
<string>Utiliser les entitées sélectionnées</string>
</property>
</widget>
<widget class="QTextEdit" name="bound_log">
<property name="geometry">
<rect>
<x>350</x>
<y>150</y>
<width>251</width>
<height>81</height>
</rect>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu Sans'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="QPushButton" name="clear_filter">
<property name="geometry">
<rect>
<x>467</x>
<y>30</y>
<width>131</width>
<height>27</height>
</rect>
</property>
<property name="text">
<string>Clear filter</string>
</property>
</widget>
<widget class="QToolButton" name="syn_tools">
<property name="geometry">
<rect>
<x>380</x>
<y>30</y>
<width>26</width>
<height>26</height>
</rect>
</property>
<property name="toolTip">
<string>Ajouter des filtres</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
<property name="autoRaise">
<bool>false</bool>
</property>
</widget>
</widget>
<widget class="QWidget" name="status">
<attribute name="title">
<string>Statuts</string>
</attribute>
<widget class="QgsMapLayerComboBox" name="map_layers">
<property name="geometry">
<rect>
<x>238</x>
<y>40</y>
<width>271</width>
<height>27</height>
</rect>
</property>
<property name="accessibleName">
<string>Couche taxonomique</string>
</property>
<property name="allowEmptyLayer">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="lab_lay_tax">
<property name="geometry">
<rect>
<x>45</x>
<y>40</y>
<width>211</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Couche taxonomique :</string>
</property>
</widget>
<widget class="QLabel" name="lab_cd_tax">
<property name="geometry">
<rect>
<x>110</x>
<y>100</y>
<width>151</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Code taxon :</string>
</property>
</widget>
<widget class="QComboBox" name="cd_taxon">
<property name="geometry">
<rect>
<x>240</x>
<y>100</y>
<width>271</width>
<height>27</height>
</rect>
</property>
<property name="toolTip">
<string>Doit correspondre au cd_nom ou cd_ref</string>
</property>
</widget>
<widget class="QLineEdit" name="layer_out">
<property name="geometry">
<rect>
<x>240</x>
<y>160</y>
<width>341</width>
<height>27</height>
</rect>
</property>
<property name="toolTip">
<string>Nom de la couche à créer</string>
</property>
<property name="text">
<string>output</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="lab_lay_out">
<property name="geometry">
<rect>
<x>80</x>
<y>160</y>
<width>191</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Name layer out :</string>
</property>
</widget>
</widget>
</widget>
<widget class="QgsAuthConfigSelect" name="mAuthConfigSelect">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>70</x>
<y>60</y>
<width>292</width>
<height>41</height>
</rect>
</property>
<property name="tabletTracking">
<bool>true</bool>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
</widget>
<widget class="QLineEdit" name="report_url">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>360</x>
<y>20</y>
<width>341</width>
<height>27</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QPushButton" name="login_gn">
<property name="geometry">
<rect>
<x>400</x>
<y>60</y>
<width>88</width>
<height>27</height>
</rect>
</property>
<property name="text">
<string>Connect</string>
</property>
</widget>
<widget class="QLabel" name="is_connect">
<property name="geometry">
<rect>
<x>500</x>
<y>60</y>
<width>201</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Non connecté..</string>
</property>
</widget>
<widget class="QComboBox" name="src_database">
<property name="geometry">
<rect>
<x>70</x>
<y>20</y>
<width>201</width>
<height>27</height>
</rect>
</property>
<property name="toolTip">
<string>Source de données</string>
</property>
<property name="currentText">
<string/>
</property>
</widget>
<widget class="QRadioButton" name="pub_access">
<property name="geometry">
<rect>
<x>280</x>
<y>20</y>
<width>111</width>
<height>31</height>
</rect>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="text">
<string>Public</string>
</property>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>QgsAuthConfigSelect</class>
<extends>QWidget</extends>
<header>qgsauthconfigselect.h</header>
</customwidget>
<customwidget>
<class>QgsMapLayerComboBox</class>
<extends>QComboBox</extends>
<header>qgsmaplayercombobox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+169
View File
@@ -0,0 +1,169 @@
#! python3 # noqa: E265
"""
Plugin settings form integrated into QGIS 'Options' menu.
"""
# standard
import platform
from functools import partial
from pathlib import Path
from urllib.parse import quote
# PyQGIS
from qgis.core import Qgis, QgsApplication
from qgis.gui import QgsOptionsPageWidget, QgsOptionsWidgetFactory
from qgis.PyQt import uic
from qgis.PyQt.QtCore import QUrl
from qgis.PyQt.QtGui import QDesktopServices, QIcon
# project
from gn_tools.__about__ import (
__icon_path__,
__title__,
__uri_homepage__,
__uri_tracker__,
__version__,
)
from gn_tools.toolbelt import PlgLogger, PlgOptionsManager
from gn_tools.toolbelt.preferences import PlgSettingsStructure
# ############################################################################
# ########## Globals ###############
# ##################################
FORM_CLASS, _ = uic.loadUiType(
Path(__file__).parent / "{}.ui".format(Path(__file__).stem)
)
# ############################################################################
# ########## Classes ###############
# ##################################
class ConfigOptionsPage(FORM_CLASS, QgsOptionsPageWidget):
"""Settings form embedded into QGIS 'options' menu."""
def __init__(self, parent):
super().__init__(parent)
self.log = PlgLogger().log
self.plg_settings = PlgOptionsManager()
# load UI and set objectName
self.setupUi(self)
self.setObjectName("mOptionsPage{}".format(__title__))
report_context_message = quote(
"> Reported from plugin settings\n\n"
f"- operating system: {platform.system()} "
f"{platform.release()}_{platform.version()}\n"
f"- QGIS: {Qgis.QGIS_VERSION}\n"
f"- plugin version: {__version__}\n"
)
# header
self.lbl_title.setText(f"{__title__} - Version {__version__}")
# customization
self.btn_help.setIcon(QIcon(QgsApplication.iconPath("mActionHelpContents.svg")))
self.btn_help.pressed.connect(
partial(QDesktopServices.openUrl, QUrl(__uri_homepage__))
)
self.btn_report.setIcon(
QIcon(QgsApplication.iconPath("console/iconSyntaxErrorConsole.svg"))
)
self.btn_reset.setIcon(QIcon(QgsApplication.iconPath("mActionUndo.svg")))
self.btn_reset.pressed.connect(self.reset_settings)
# load previously saved settings
self.load_settings()
def apply(self):
"""Called to permanently apply the settings shown in the options page (e.g. \
save them to QgsSettings objects). This is usually called when the options \
dialog is accepted."""
settings = self.plg_settings.get_plg_settings()
# misc
settings.debug_mode = self.opt_debug.isChecked()
settings.version = __version__
# param
settings.domain_url = self.edit_url.text()
# dump new settings into QgsSettings
self.plg_settings.save_from_object(settings)
if __debug__:
self.log(
message="DEBUG - Settings successfully saved.",
log_level=Qgis.MessageLevel.NoLevel,
)
def load_settings(self):
"""Load options from QgsSettings into UI form."""
settings = self.plg_settings.get_plg_settings()
# global
self.opt_debug.setChecked(settings.debug_mode)
self.lbl_version_saved_value.setText(settings.version)
# param
self.edit_url.setText(settings.domain_url)
def reset_settings(self):
"""Reset settings to default values (set in preferences.py module)."""
default_settings = PlgSettingsStructure()
# dump default settings into QgsSettings
self.plg_settings.save_from_object(default_settings)
# update the form
self.load_settings()
class PlgOptionsFactory(QgsOptionsWidgetFactory):
"""Factory for options widget."""
def __init__(self):
"""Constructor."""
super().__init__()
def icon(self) -> QIcon:
"""Returns plugin icon, used to as tab icon in QGIS options tab widget.
:return: _description_
:rtype: QIcon
"""
return QIcon(str(__icon_path__))
def createWidget(self, parent) -> ConfigOptionsPage:
"""Create settings widget.
:param parent: Qt parent where to include the options page.
:type parent: QObject
:return: options page for tab widget
:rtype: ConfigOptionsPage
"""
return ConfigOptionsPage(parent)
def title(self) -> str:
"""Returns plugin title, used to name the tab in QGIS options tab widget.
:return: plugin title from about module
:rtype: str
"""
return __title__
def helpId(self) -> str:
"""Returns plugin help URL.
:return: plugin homepage url from about module
:rtype: str
"""
return __uri_homepage__
+255
View File
@@ -0,0 +1,255 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>wdg_gn_tools_settings</class>
<widget class="QWidget" name="wdg_gn_tools_settings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>638</width>
<height>325</height>
</rect>
</property>
<property name="windowTitle">
<string>gn_tools - Settings</string>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="lbl_title">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;PluginTitle - Version X.X.X&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>false</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="grp_misc">
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="title">
<string>Miscellaneous</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QPushButton" name="btn_help">
<property name="minimumSize">
<size>
<width>200</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>30</height>
</size>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="text">
<string>Help</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="lbl_version_saved_value">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="text">
<string notr="true">X.X.X</string>
</property>
<property name="textInteractionFlags">
<set>Qt::NoTextInteraction</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lbl_version_saved">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="text">
<string>Version used to save settings:</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QPushButton" name="btn_reset">
<property name="minimumSize">
<size>
<width>200</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="text">
<string>Reset settings to factory defaults</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="opt_debug">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>Enable debug mode.</string>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="text">
<string>Debug mode (degraded performances)</string>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="btn_report">
<property name="minimumSize">
<size>
<width>200</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>30</height>
</size>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="text">
<string>Report an issue</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="edit_url"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lab_url_gn">
<property name="text">
<string>Url GéoNature :</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>56</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+45
View File
@@ -0,0 +1,45 @@
[general]
name=gn_tools
about=Bienvenue dans le GéoNature du Conservatoire des Espaces Naturels de l'Isère
category=None
hasProcessingProvider=True
description=Extension QGIS permettant de récupérer des données issue de Géonature
icon=resources/images/Taxon_icon_vert.svg
tags=GéoNature, api, tools
# credits and contact
author=Colas GEIER
email=geomatique@cen-isere.org
homepage=https://gitea.cenra-outils.org/CEN38/gn_tools/pages
repository=https://gitea.cenra-outils.org/CEN38/gn_tools
tracker=None
# QGIS context
deprecated=False
experimental=True
plugin_dependencies=
qgisMinimumVersion=3.30
qgisMaximumVersion=3.99
supportsQt6=True
# versioning
version=0.1.0
changelog=
[service]
domain_name=https://geonature.cen-isere.fr/geonature
biodiv_aura=https://donnees.biodiversite-auvergne-rhone-alpes.fr
[ref_taxon]
biodiv_aura=https://taxons.biodiversite-aura.fr
[api]
login_api=api/auth/login
[export]
except_export_term=["sinp","synthese_ens_departement"]
synth_web=api/synthese/for_web
synth_exp=api/synthese/export_observations
lstexport=api/exports/?search=&per_page=100&page=1
cd_status=api/taxhub/api/taxref/
+566
View File
@@ -0,0 +1,566 @@
#! python3 # noqa: E265
"""Main plugin module."""
# standard
from functools import partial
from pathlib import Path
from typing import Optional
# PyQGIS
from qgis.core import (
Qgis,
QgsApplication,
QgsSettings,
QgsProject,
QgsGeometry,
QgsMapLayerProxyModel,
QgsCoordinateReferenceSystem,
QgsCoordinateTransform,
)
from qgis.gui import QgisInterface
from qgis.PyQt.QtCore import QCoreApplication, QLocale, QTranslator, QUrl
from qgis.PyQt.QtGui import QDesktopServices, QIcon
from qgis.PyQt.QtWidgets import QAction, QMessageBox
from qgis.PyQt.QtNetwork import QNetworkAccessManager
from PyQt5.QtWidgets import QCheckBox, QApplication
# project
from gn_tools.__about__ import (
DIR_PLUGIN_ROOT,
__icon_path__,
__title__,
__uri_homepage__,
__domain_url__,
__biodiv_aura__,
__gbif__,
__api_sn_web__,
__api_sn_exp__,
__api_lst_ex__,
__api_cd_sta__,
)
from gn_tools.gui.dlg_settings import PlgOptionsFactory, PlgOptionsManager
from gn_tools.gui.dlg_gn_tools import gnToolsDialog
from gn_tools.gui.dlg_gn_synthese import gnToolsSynthese
from gn_tools.processing import (
GnToolsProvider,
GetLogin,
ParamWidget,
LoadingImport,
RectangleFeatureTool,
PivotStatus,
GetFilterFromAPI,
)
from gn_tools.toolbelt import PlgLogger
# ############################################################################
# ########## Classes ###############
# ##################################
class GnToolsPlugin:
def __init__(self, iface: QgisInterface):
"""Constructor.
:param iface: An interface instance that will be passed to this class which \
provides the hook by which you can manipulate the QGIS application at run time.
:type iface: QgsInterface
"""
self.iface = iface
self.canvas = iface.mapCanvas()
self.project = QgsProject.instance()
self.log = PlgLogger().log
self.provider: Optional[GnToolsProvider] = None
self.rectangle = None
self.src_bdd = {
'GéoNature':__domain_url__,
"Biodiv'AuRA":__biodiv_aura__
}
self.syn_params = {}
# translation
# initialize the locale
self.locale: str = QgsSettings().value("locale/userLocale", QLocale().name())[
0:2
]
locale_path: Path = (
DIR_PLUGIN_ROOT
/ "resources"
/ "i18n"
/ f"{__title__.lower()}_{self.locale}.qm"
)
self.log(message=f"Translation: {self.locale}, {locale_path}", log_level=Qgis.MessageLevel.NoLevel)
if locale_path.exists():
self.translator = QTranslator()
self.translator.load(str(locale_path.resolve()))
QCoreApplication.installTranslator(self.translator)
lst_src = []
for src,url in self.src_bdd.items():
if url != '':
self.src_bdd[src] = url + '/' if url[-1] != '/' else url
lst_src += [src]
# self.domain_url = self.src_bdd['GéoNature']
self.dlg = gnToolsDialog()
self.dlg_syn = gnToolsSynthese()
# Masquer les widgets
self.dlg.pub_access.setVisible(False)
# self.dlg.syn_tools.setVisible(False)
self.dlg_syn.date_min.clear()
self.dlg_syn.date_max.clear()
self.dlg.status.setDisabled(True)
self.dlg.src_database.addItems(['',*lst_src])
self.dlg.src_database.currentTextChanged.connect(
self.define_domain_url
)
# Biodiv'AuRA check box action
self.dlg.pub_access.toggled.connect(
self.dlg.mAuthConfigSelect.setEnabled
)
self.dlg.pub_access.toggled.connect(
self.dlg.mAuthConfigSelect.setDisabled
)
# Import the extent drawer tool and connect a signal to make the window appear once the extent is drawned
self.move_tool = RectangleFeatureTool(self.project, self.canvas)
self.move_tool.signal.connect(self.activate_window)
# Login to GN
self.dlg.login_gn.clicked.connect(self.connect_src)
# Filter Active Layer
self.dlg.draw_bounds.setDisabled(True)
self.dlg.bound_log.setReadOnly(True)
self.dlg.bound_log.setPlainText('')
self.dlg.draw_bounds.clicked.connect(self.pointer)
self.dlg.active_filter_layer.setDisabled(True)
self.dlg.active_filter_layer.clicked.connect(self.filter_layer_activate)
self.dlg.filter_layer.setDisabled(True)
self.dlg.use_entity.setDisabled(True)
self.dlg.use_entity.setChecked(False)
self.dlg.map_layers.setFilters(QgsMapLayerProxyModel.VectorLayer)
self.dlg.map_layers.setAllowEmptyLayer(True)
self.filter_layers()
self.canvas.layersChanged.connect(self.filter_layers)
self.dlg.filter_layer.setFilters(QgsMapLayerProxyModel.VectorLayer)
self.dlg.filter_layer.setAllowEmptyLayer(True)
self.dlg.filter_layer.layerChanged.connect(self.extend_layer)
self.dlg.map_layers.layerChanged.connect(self.param_cd_taxon)
# Filtre de la synthèse
self.dlg.syn_tools.clicked.connect(self.dlg_syn.show)
# Clear button
self.dlg.clear_filter.setDisabled(True)
self.dlg.clear_filter.clicked.connect(self.clear_filter)
# Finished Button
self.dlg.buttonBox.accepted.disconnect()
self.dlg.buttonBox.accepted.connect(self.accept)
self.dlg.buttonBox.rejected.connect(self.reject)
def initGui(self):
"""Set up plugin UI elements."""
self.manager = QNetworkAccessManager()
self.manager.accepted = False
# settings page within the QGIS preferences menu
self.options_factory = PlgOptionsFactory()
self.iface.registerOptionsWidgetFactory(self.options_factory)
# -- Actions
self.action_help = QAction(
QgsApplication.getThemeIcon("mActionHelpContents.svg"),
self.tr("Help"),
self.iface.mainWindow(),
)
self.action_help.triggered.connect(
partial(QDesktopServices.openUrl, QUrl(__uri_homepage__))
)
self.action_settings = QAction(
QgsApplication.getThemeIcon("console/iconSettingsConsole.svg"),
self.tr("Settings"),
self.iface.mainWindow(),
)
self.action_settings.triggered.connect(
lambda: self.iface.showOptionsDialog(
currentPage="mOptionsPage{}".format(__title__)
)
)
self.action_gn_tools = QAction(
QIcon(str(__icon_path__)),
f"{__title__} - Import project",
self.iface.mainWindow(),
)
self.iface.addToolBarIcon(self.action_gn_tools)
self.action_gn_tools.triggered.connect(
lambda: self.run_gn_tools()
)
# -- Menu
self.iface.addPluginToMenu(__title__, self.action_gn_tools)
self.iface.addPluginToMenu(__title__, self.action_settings)
self.iface.addPluginToMenu(__title__, self.action_help)
# -- Processing
self.initProcessing()
# -- Help menu
# documentation
self.iface.pluginHelpMenu().addSeparator()
self.action_help_plugin_menu_documentation = QAction(
QIcon(str(__icon_path__)),
f"{__title__} - Documentation",
self.iface.mainWindow(),
)
self.action_help_plugin_menu_documentation.triggered.connect(
partial(QDesktopServices.openUrl, QUrl(__uri_homepage__))
)
self.iface.pluginHelpMenu().addAction(
self.action_help_plugin_menu_documentation
)
self.dlg.is_connect.setText('Non connecté..')
def initProcessing(self):
"""Initialize the processing provider."""
self.provider = GnToolsProvider()
QgsApplication.processingRegistry().addProvider(self.provider)
def tr(self, message: str) -> str:
"""Get the translation for a string using Qt translation API.
:param message: string to be translated.
:type message: str
:returns: Translated version of message.
:rtype: str
"""
return QCoreApplication.translate(self.__class__.__name__, message)
def unload(self):
"""Cleans up when plugin is disabled/uninstalled."""
# -- Clean up toolbar
self.iface.removeToolBarIcon(self.action_gn_tools)
# -- Clean up menu
self.iface.removePluginMenu(__title__, self.action_gn_tools)
self.iface.removePluginMenu(__title__, self.action_help)
self.iface.removePluginMenu(__title__, self.action_settings)
# -- Clean up preferences panel in QGIS settings
self.iface.unregisterOptionsWidgetFactory(self.options_factory)
# -- Unregister processing
QgsApplication.processingRegistry().removeProvider(self.provider)
# remove from QGIS help/extensions menu
if self.action_help_plugin_menu_documentation:
self.iface.pluginHelpMenu().removeAction(
self.action_help_plugin_menu_documentation
)
# remove actions
del self.action_gn_tools
del self.action_settings
del self.action_help
def run(self):
"""Main process.
:raises Exception: if there is no item in the feed
"""
try:
self.log(
message=self.tr("Everything ran OK."),
log_level=Qgis.MessageLevel.Success,
push=False,
)
print("tom")
except Exception as err:
self.log(
message=self.tr("Houston, we've got a problem: {}".format(err)),
log_level=Qgis.MessageLevel.Critical,
push=True,
)
def define_domain_url(self):
self.src_name = self.dlg.src_database.currentText()
if self.src_name != '':
self.domain_url = self.src_bdd[self.src_name]
self.dlg.report_url.setText(self.domain_url[:42])
if self.src_name == 'GBIF':
self.dlg.mAuthConfigSelect.setDisabled(True)
self.dlg.login_gn.setDisabled(True)
self.dlg.is_connect.setText('No connection need')
self.print_change()
ParamWidget(
parent=self,network_manager=self.manager
)
else:
self.dlg.mAuthConfigSelect.setEnabled(True)
self.dlg.login_gn.setEnabled(True)
self.dlg.is_connect.setText('Non connecté..')
if self.src_name == "Biodiv'AuRA":
self.dlg.pub_access.setVisible(True)
self.dlg.status.setDisabled(True)
else :
self.dlg.pub_access.setVisible(False)
self.dlg.pub_access.setChecked(False)
self.dlg.status.setEnabled(True)
else:
self.dlg.clear_filter.setDisabled(True)
self.dlg.draw_bounds.setDisabled(True)
self.dlg.active_filter_layer.setDisabled(True)
self.dlg.pub_access.setVisible(False)
self.dlg.pub_access.setChecked(False)
self.dlg.mAuthConfigSelect.setDisabled(True)
self.dlg.login_gn.setDisabled(True)
self.dlg.status.setDisabled(True)
self.dlg.is_connect.setText('Non connecté..')
def filter_layers(self):
checked_layers = self.canvas.layers()
excluded_layers = [l for l in QgsProject.instance().mapLayers().values() if not l in checked_layers]
self.dlg.map_layers.setExceptedLayerList(excluded_layers)
self.dlg.filter_layer.setExceptedLayerList(excluded_layers)
self.param_cd_taxon()
return excluded_layers
def run_gn_tools(self):
"""Main process. Create a QGIS project with ZH data from GeoNature."""
print("****** CREATE PROJECT ******")
# Get base url for all the request from the dlg settings
self.plg_settings = PlgOptionsManager().get_plg_settings()
if self.plg_settings.domain_url:
self.domain_url = self.plg_settings.domain_url
else :
self.domain_url = __domain_url__
if self.domain_url[-1] != '/':
self.domain_url = self.domain_url + '/'
# self.study_area_list = self.plg_settings.study_area_list
# self.dl_zh_pot = self.plg_settings.dl_zh_pot
# self.export_number = self.plg_settings.export_number
if self.domain_url == "":
msg = QMessageBox()
msg.warning(
None,
self.tr("Warning"),
self.tr("You need to inform GeoNature's URL"), # noqa: E501
)
self.iface.showOptionsDialog(currentPage="mOptionsPage{}".format(__title__))
self.run_gn_tools()
else:
# Initiate class
print(self.domain_url)
print(self.dlg.src_database.currentText())
if self.dlg.src_database.currentText() != '':
api = self.src_bdd[self.dlg.src_database.currentText()]
self.dlg.report_url.setText(api[:42])
self.domain_url = api
self.src_name = self.dlg.src_database.currentText()
self.dlg.show()
# Run the dialog event loop
result = self.dlg.exec_()
if result:
# Do something useful here - delete the line containing pass and
# substitute with you
print('ok')
print(result)
pass
def connect_src(self):
print('connect_src : ',self.src_name)
if self.src_name == 'GéoNature':
self.getLogin = GetLogin(parent=self,network_manager=self.manager)
self.getLogin.finished_login.connect(self.param_widget)
elif self.src_name == "Biodiv'AuRA":
if self.dlg.pub_access.isChecked():
user = {
'usr': 'user-public',
'pwd': 'public'
}
self.getLogin = GetLogin(parent=self,network_manager=self.manager,params=user)
self.getLogin.finished_login.connect(self.param_widget)
else :
self.getLogin = GetLogin(parent=self,network_manager=self.manager)
self.getLogin.finished_login.connect(self.param_widget)
# if self.manager.cookieJar():
# self.dlg.is_connect.setText("Good Job !")
# else:
# self.dlg.is_connect.setText("Aie !")
def param_widget(self):
print(self.manager.cookieStatus)
if self.manager.cookieStatus==200:
self.paramWidget = ParamWidget(
parent=self,network_manager=self.manager
)
self.dlg.source_import.currentTextChanged.connect(
self.print_change
)
def param_cd_taxon(self):
layer = self.dlg.map_layers.currentLayer()
if layer:
cols_name = layer.fields().names()
self.dlg.cd_taxon.clear()
self.dlg.cd_taxon.addItems(cols_name)
def print_change(self):
self.dlg.clear_filter.setEnabled(True)
self.dlg.draw_bounds.setEnabled(True)
self.dlg.active_filter_layer.setEnabled(True)
txt = self.dlg.source_import.currentText()
self.dlg.syn_tools.setVisible(True
if txt == 'Synthèse taxons' else False
)
if txt == 'Synthèse taxons':
self.syn = GetFilterFromAPI(
parent=self, network_manager=self.manager
)
print(self.dlg.source_import.currentIndex())
def pointer(self):
self.dlg.filter_layer.setDisabled(True)
self.dlg.use_entity.setDisabled(True)
self.dlg.use_entity.setChecked(False)
self.dlg.bound_log.setPlainText('')
# Add the tool to draw a rectangle
self.iface.mainWindow().activateWindow()
self.canvas.setMapTool(self.move_tool)
def activate_window(self):
# Put the dialog on top once the rectangle is drawn
# self.activateWindow()
self.canvas.unsetMapTool(self.move_tool)
self.check_valid()
def check_valid(self):
self.rectangle = QgsGeometry.fromRect(self.move_tool.new_extent)
sourceCrs = QgsCoordinateReferenceSystem(self.project.crs())
destCrs = QgsCoordinateReferenceSystem(4326)
tr = QgsCoordinateTransform(sourceCrs, destCrs, self.project)
self.rectangle.transform(tr)
self.dlg.bound_log.setPlainText(
'Bound selected : '
+ str(self.rectangle.asWkt())
)
print('rectangle : ',self.rectangle.asJson())
def filter_layer_activate(self):
self.dlg.bound_log.setPlainText('')
self.extend_layer()
self.dlg.filter_layer.setEnabled(True)
self.dlg.use_entity.setEnabled(True)
self.canvas.unsetMapTool(self.move_tool)
def extend_layer(self):
if self.dlg_syn.jdd_box.checkedItemsData():
print('checked : ',self.dlg_syn.jdd_box.checkedItems())
print('checked : ',self.syn_params)
if self.dlg.filter_layer.currentText() != '':
layer = self.dlg.filter_layer.currentLayer()
destCrs = QgsCoordinateReferenceSystem(4326)
self.rectangle = QgsGeometry.fromRect(
layer.extent()
)
if self.dlg.use_entity.isChecked():
geoms = [f.geometry() for f in layer.getSelectedFeatures()]
if geoms:
union = QgsGeometry.unaryUnion(geoms)
self.rectangle = QgsGeometry.fromRect(
union.boundingBox()
)
if layer.crs() != destCrs:
tr = QgsCoordinateTransform(layer.crs(), destCrs, self.project)
self.rectangle.transform(tr)
print(layer.extent())
print(layer.crs())
def clear_filter(self):
self.dlg.bound_log.setPlainText('')
self.rectangle = None
def accept(self):
currentTab = self.dlg.tab.currentWidget().objectName()
if self.manager.accepted or self.src_name=='GBIF' :
self.dlg.buttonBox.blockSignals(False)
if currentTab == 'import_2':
load = LoadingImport(
parent=self,network_manager=self.manager
)
# load.finished_dl.connect()
elif currentTab == 'status':
PivotStatus(
parent=self,network_manager=self.manager,
layer=self.dlg.map_layers.currentLayer(),
cd_nom=self.dlg.cd_taxon.currentText(),
)
# print("Work in progress ..")
# msg = QMessageBox()
# msg.warning(
# None,
# self.tr("Warning"),
# self.tr("Work in progress .."), # noqa: E501
# )
print("Accepted")
else:
msg = QMessageBox()
msg.warning(
None,
self.tr("Warning"),
self.tr("Opération impossible ! \nVous n'êtes pas connectez à GéoNature"), # noqa: E501
)
self.dlg.show()
def reject(self):
QApplication.restoreOverrideCursor()
print("Rejected")
+8
View File
@@ -0,0 +1,8 @@
#! python3 # noqa: E265
from .provider import GnToolsProvider # noqa: F401
from .login import GetLogin
from .widget import ParamWidget, LoadingImport
from .draw import RectangleFeatureTool
from .gbif import ImportDataGbif
from .status import PivotStatus
from .filter_synthese import GetFilterFromAPI
+104
View File
@@ -0,0 +1,104 @@
# Import PyQt libs
from PyQt5.QtCore import (
pyqtSignal,
Qt,
)
from PyQt5.QtGui import QColor
# Import PyQgis libs
from qgis.core import (
QgsRectangle,
QgsPointXY,
QgsWkbTypes,
QgsCoordinateReferenceSystem,
QgsCoordinateTransform,
)
from qgis.gui import (
QgsMapTool,
QgsMapMouseEvent,
QgsRubberBand,
)
class RectangleFeatureTool(QgsMapTool):
signal = pyqtSignal()
def __init__(self, project, canvas):
super().__init__(canvas)
self.project = project
self.canvas = canvas
# initiate variable to calculate the extent
self.start_point = None
self.end_point = None
self.new_extent = None
# flag to check if the left mouse button was pressed
self.is_left_button_pressed = False
# create a rubber line object
# to display the geometry of the dragged object on the canvas
self.rubber_band = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
self.rubber_band.setColor(QColor(255, 0, 0, 50))
self.rubber_band.setWidth(2)
# Mouse button pressed.
def canvasPressEvent(self, event: QgsMapMouseEvent):
if event.button() == Qt.LeftButton:
self.start_point = self.toMapCoordinates(event.pos())
self.end_point = self.start_point
self.is_left_button_pressed = True
# The cursor moves across the screen.
def canvasMoveEvent(self, event: QgsMapMouseEvent):
# if the left mouse button is pressed
if self.is_left_button_pressed:
self.end_point = self.toMapCoordinates(event.pos())
# draw a rectangle on the canvas
self.showRect(self.start_point, self.end_point)
# The mouse button is released.
def canvasReleaseEvent(self, event: QgsMapMouseEvent):
# if the left mouse button was released
if event.button() == Qt.LeftButton:
if event.type() == 3:
# get the rectangle created from the click point and
# left mouse button release points
self.new_extent = self.rectangle()
self.signal.emit()
# toggle the flag, the left mouse button is no longer held down
self.is_left_button_pressed = False
self.rubber_band.reset() # remove rubber band from canvas
sourceCrs = QgsCoordinateReferenceSystem(self.project.crs())
destCrs = QgsCoordinateReferenceSystem(4326)
tr = QgsCoordinateTransform(sourceCrs, destCrs, self.project)
# Draw the rectangle on the canvas
def showRect(self, startPoint, endPoint):
# Reset the last drawn rectangle
self.rubber_band.reset(QgsWkbTypes.PolygonGeometry)
# Make sure the mouse has moved
if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
return
# Create the rectangle
point1 = QgsPointXY(startPoint.x(), startPoint.y())
point2 = QgsPointXY(startPoint.x(), endPoint.y())
point3 = QgsPointXY(endPoint.x(), endPoint.y())
point4 = QgsPointXY(endPoint.x(), startPoint.y())
self.rubber_band.addPoint(point1, False)
self.rubber_band.addPoint(point2, False)
self.rubber_band.addPoint(point3, False)
self.rubber_band.addPoint(point4, True) # true to update canvas
self.rubber_band.show()
def rectangle(self):
if self.start_point is None or self.end_point is None:
return None
elif self.start_point.x() == self.end_point.x() or self.start_point.y() == self.end_point.y():
return None
return QgsRectangle(self.start_point, self.end_point)
def deactivate(self):
# unload the map tool on the mouse
QgsMapTool.deactivate(self)
self.deactivated.emit()
+227
View File
@@ -0,0 +1,227 @@
# Standard
import json
# PyQt
from qgis.core import QgsAuthMethodConfig, QgsDataSourceUri
from qgis.PyQt.QtCore import QByteArray, QJsonDocument, QObject, QUrl, pyqtSignal, Qt
from qgis.PyQt.QtNetwork import QNetworkReply, QNetworkRequest
from qgis.PyQt.QtWidgets import QAction, QMessageBox, QDialog, QApplication
# Project
from gn_tools.__about__ import (
__orb_tax_url__
)
class GetFilterFromAPI(QDialog):
finished_params = pyqtSignal()
"""
Create a filter, specific to GeoNature ZH API and fetch all the features corresponding.
"""
def __init__(
self,
parent=None,
network_manager=None
):
super().__init__()
print("enfin !")
self.domain_url = parent.domain_url
self.tax_url = self.domain_url if parent.src_name == 'GéoNature' else __orb_tax_url__
if self.tax_url[-1] != '/':
self.tax_url += '/'
self.network_manager = network_manager
self.auth = parent.dlg.mAuthConfigSelect
self.is_connect = parent.dlg.is_connect
self.dlg = parent.dlg_syn
self.parent = parent
self.cookie = (self.network_manager.cookieJar()
.cookiesForUrl(QUrl(self.domain_url))[0]
)
# PyQt5 - Version
# PyQt6 - Version
# QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
self._pending_downloads = 0
# self.dlg.send_to_gn.
self.dlg.send_to_gn.setText(
self.dlg.send_to_gn.text() + '<br><br><a href="{url}">{url}</a>'.format(url=self.domain_url)
)
self.dlg.send_to_gn.setStyleSheet(
"border: 1px solid; border-color:blue; font-weight:bold"
)
self.dlg.send_to_gn.setOpenExternalLinks(True)
self.params = {}
self.get_jdd()
if self.parent.src_name == 'GéoNature':
self.get_phylog()
else:
self.self.dlg.regne.deselectAllOptions()
self.self.dlg.regne.clear()
self.dlg.buttonBox.accepted.connect(self.accept)
self.dlg.buttonBox.rejected.connect(self.reject)
@property
def pending_downloads(self):
return self._pending_downloads
def handle_finished(self, reply):
self._pending_downloads -= 1
# If reply is an error or reply is a redirection, login is not accepted
print(reply.attribute(
QNetworkRequest.HttpStatusCodeAttribute
))
print(reply.error(),' : ',reply.dtype)
self.network_manager.cookieStatus = reply.attribute(
QNetworkRequest.HttpStatusCodeAttribute
)
QApplication.restoreOverrideCursor()
if (
reply.error() != QNetworkReply.NoError
or self.network_manager.cookieStatus != 200
):
# Error code 299 means the logins are invalid
# Redirection not equal to 200 means a redirection happened
if reply.dtype == 'JDD':
self.dlg.jdd_box.setDisabled(True)
self.dlg.jdd_box.clear()
elif reply.dtype in ['PHYLOG','TAX_TREE']:
self.dlg.regne.setDisabled(True)
self.dlg.regne.clear()
if reply.error() == 0:
msg = QMessageBox()
msg.warning(
None,
self.tr("Warning"),
self.tr(
"Houston, we've got a problem ... \n\n L'utilisateur ou le mot de passe est invalide !"
), # noqa: E501
)
else:
print(f"code: {reply.error()} message: {reply.errorString()}")
else:
data_request = reply.readAll().data().decode()
data = json.loads(data_request)
self.params[reply.dtype] = data
self.close()
if self.pending_downloads == 1 :
self.get_taxon_tree()
if self.pending_downloads == 0:
self.set_params_to_widget()
self.finished_params.emit()
QApplication.restoreOverrideCursor()
def get_jdd(self):
self._pending_downloads += 1
QApplication.setOverrideCursor(Qt.WaitCursor)
self.dlg.jdd_box.setEnabled(True)
url_api = self.domain_url + 'api/meta/datasets?'
if self.parent.src_name == 'GéoNature':
url_api += 'orderby=dataset_shortname'
request_type = 'POST'
elif self.parent.src_name == "Biodiv'AuRA":
url_api += 'orderby=dataset_name'
request_type = 'GET'
url = QUrl(url_api)
self.send_request(url,type_request=request_type,type_param='JDD')
def get_phylog(self):
self._pending_downloads += 2
QApplication.setOverrideCursor(Qt.WaitCursor)
self.dlg.regne.setEnabled(True)
if self.parent.src_name == 'GéoNature':
url_api = self.tax_url + 'api/taxhub/api/taxref/regnewithgroupe2'
if self.parent.src_name == "Biodiv'AuRA":
url_api = self.tax_url + 'api/taxref/regnewithgroupe2'
url = QUrl(url_api)
self.send_request(url,type_request='GET',type_param='PHYLOG')
def get_taxon_tree(self):
QApplication.setOverrideCursor(Qt.WaitCursor)
if self.parent.src_name == 'GéoNature':
url_api = self.tax_url + 'api/synthese/taxons_tree'
url = QUrl(url_api)
self.send_request(url,type_request='GET',type_param='TAX_TREE')
else:
self._pending_downloads -= 1
self.set_params_to_widget()
self.finished_params.emit()
QApplication.restoreOverrideCursor()
def send_request(self,url,type_request,type_param):
print(url)
request = QNetworkRequest(url)
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
request.setHeader(QNetworkRequest.CookieHeader, self.cookie)
if type_request == 'GET':
reply = self.network_manager.get(request)
elif type_request == 'POST':
doc = QJsonDocument({})
reply = self.network_manager.post(request,doc.toJson())
reply.dtype = type_param
reply.finished.connect(lambda: self.handle_finished(reply))
def set_params_to_widget(self):
if 'JDD' in self.params.keys():
jdd_items = [i['dataset_shortname'] for i in self.params['JDD']]
self.dlg.jdd_box.addItems(jdd_items)
if 'PHYLOG' in self.params.keys():
rgn_items = [i for i in self.params['PHYLOG'].keys()]
self.dlg.regne.addItems(rgn_items)
# self.dlg.jdd_box.setCheckedItems(items)
print('END loading params ...')
def accept(self):
params = {
'id_dataset':[ i['id_dataset']
for i in self.params['JDD']
if i['dataset_shortname'] in self.dlg.jdd_box.checkedItems()
] if 'JDD' in self.params.keys() else [],
'cd_ref_parent': [ i['cd_ref']
for i in self.params['TAX_TREE']
if i['nom_regne'] in self.dlg.regne.checkedItems()
] if 'PHYLOG' in self.params.keys() else [],
'date_min' : None if self.dlg.date_min.isNull()
else self.dlg.date_min.date().toString('yyyy-MM-dd'),
'date_max' : None if self.dlg.date_max.isNull()
else self.dlg.date_max.date().toString('yyyy-MM-dd'),
}
self.parent.syn_params = {
i : params[i] for i in params if params[i]
}
def reject(self):
self.clear_widgets()
QApplication.restoreOverrideCursor()
print("Rejected")
def clear_widgets(self):
self.dlg.jdd_box.deselectAllOptions()
self.dlg.regne.deselectAllOptions()
self.dlg.date_min.clear()
self.dlg.date_max.clear()
+205
View File
@@ -0,0 +1,205 @@
# Import basic libs
import json
import time
# Import PyQt libs
from PyQt5.QtCore import QObject, pyqtSignal, QUrl, Qt, QVariant
from PyQt5.QtGui import QColor
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
from PyQt5.QtWidgets import QDialog, QPushButton, QApplication, QVBoxLayout, QHBoxLayout, QButtonGroup, QCheckBox, QLabel
# Import PyQgis libs
from qgis.utils import iface
from qgis.core import QgsProject, QgsFeature, QgsDataSourceUri, QgsRectangle, QgsNetworkAccessManager, QgsVectorLayer, QgsPointXY, QgsWkbTypes, QgsMapLayerProxyModel, QgsGeometry, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsField
from qgis.gui import QgsMapTool, QgsMapMouseEvent, QgsRubberBand, QgsMapLayerComboBox
class GetTaxrefId(QObject):
finished_dl = pyqtSignal()
"""Get multiples informations from a getcapabilities request.
List all layers available, get the maximal extent of all the Wfs' data."""
def __init__(self, network_manager=None, project=None, layer=None, field_name=None, field_rank=None):
super().__init__()
self.network_manager = network_manager
self.project = project
self.layer = layer
self.field_name = field_name
self.field_rank = field_rank
self._pending_downloads = 0
self.ids = []
self.names = []
self.rank = []
for obs in self.layer.getFeatures():
self.ids.append(obs.id())
self.names.append(obs[self.field_name])
self.rank.append(obs[self.field_rank])
self._pending_downloads = len(self.names)
self._iterate_names = 0
self.download(self.ids[self._iterate_names], self.names[self._iterate_names], self.rank[self._iterate_names])
@property
def pending_downloads(self):
return self._pending_downloads
@property
def iterate_names(self):
return self._iterate_names
def download(self, feature_id, nom, rank):
if rank.lower() == 'stateofmatter':
self._iterate_names += 1
self.download(self.ids[self._iterate_names], self.names[self._iterate_names], self.rank[self._iterate_names])
else:
if rank.lower() == 'complex' or rank.lower() == 'hybrid':
rank = 'species'
elif rank.lower() == 'epifamily' or rank.lower() == 'section':
rank = 'genus'
elif rank.lower() == 'subtribe':
rank = 'subfamily'
url = "https://api.checklistbank.org/nameusage/search?content=SCIENTIFIC_NAME&datasetKey=2008&facet=datasetKey&facet=rank&facet=issue&facet=status&facet=nomStatus&facet=nameType&facet=field&facet=authorship&facet=authorshipYear&facet=extinct&facet=environment&facet=origin&limit=50&offset=0&q={nom}&rank={rank}&type=PREFIX".format(nom=nom.split("(")[0], rank=rank.lower())
print(url)
request = QNetworkRequest(QUrl(url))
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
reply = self.network_manager.get(request)
reply.finished.connect(lambda: self.handle_finished(reply, feature_id))
self._iterate_names += 1
def handle_finished(self, reply, feature_id):
self._pending_downloads -= 1
if reply.error() != QNetworkReply.NoError:
print(f"code: {reply.error()} message: {reply.errorString()}")
if reply.error() == 403:
print("Service down")
else:
data_request = reply.readAll().data().decode()
if data_request != "":
res = json.loads(data_request)
if "result" in res:
taxref_id = res["result"][0]["id"]
taxref_name = res["result"][0]["usage"]["label"]
self.layer.startEditing()
self.layer.changeAttributeValue(feature_id, self.layer.fields().indexFromName("cd_nom"), taxref_id)
self.layer.changeAttributeValue(feature_id, self.layer.fields().indexFromName("nom_scien"), taxref_name)
self.layer.commitChanges()
self.layer.triggerRepaint()
if self.pending_downloads == 0:
self.project.addMapLayer(self.layer)
self.finished_dl.emit()
else:
self.download(self.ids[self._iterate_names], self.names[self._iterate_names], self.rank[self._iterate_names])
class ImportDataGbif(QObject):
finished_dl = pyqtSignal()
"""Get multiples informations from a getcapabilities request.
List all layers available, get the maximal extent of all the Wfs' data."""
def __init__(self, network_manager=None, project=None, geom=None, ):
super().__init__()
self._pending_downloads = 0
self._pending_pages = 1
self.network_manager = network_manager
self.project = project
self.layer = self.new_layer()
self.geom = geom
self.new_features = []
self.max_obs = 0
self.total_pages = 0
self.download()
@property
def pending_downloads(self):
return self._pending_downloads
@property
def pending_pages(self):
return self._pending_pages
def new_layer(self):
geom_type = "Point"
new_layer = QgsVectorLayer(geom_type + "?crs=epsg:4326", "GBIF", "memory")
new_layer.startEditing()
new_layer.addAttribute(QgsField("id", QVariant.String, "string", 254))
new_layer.addAttribute(QgsField("cd_nom", QVariant.Int, "integer", 10))
new_layer.addAttribute(QgsField("niveau", QVariant.String, "string", 254))
new_layer.addAttribute(QgsField("nom", QVariant.String, "string", 254))
new_layer.addAttribute(QgsField("nom_scien", QVariant.String, "string", 100000))
new_layer.addAttribute(QgsField("observat", QVariant.String, "string", 254))
new_layer.addAttribute(QgsField("date", QVariant.String, "string", 254))
new_layer.addAttribute(QgsField("projet", QVariant.String, "string", 254))
new_layer.addAttribute(QgsField("effectif", QVariant.Int, "integer", 10))
new_layer.addAttribute(QgsField("url", QVariant.String, "string", 254))
new_layer.commitChanges()
new_layer.triggerRepaint()
return new_layer
def download(self):
url = "https://api.gbif.org/v1/occurrence/search?advanced=false&geometry={polygon}&offset={offset}&limit=1000".format(
offset=self.pending_pages, polygon=self.geom.asWkt())
url = QUrl(url)
self.nb_request = 1
print(url)
request = QNetworkRequest(url)
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
reply = self.network_manager.get(request)
reply.finished.connect(lambda: self.handle_finished(reply))
self._pending_downloads += 1
def handle_finished(self, reply):
self._pending_downloads -= 1
if reply.error() != QNetworkReply.NoError:
print(f"code: {reply.error()} message: {reply.errorString()}")
if reply.error() == 403:
print("Service down")
else:
data_request = reply.readAll().data().decode()
if self.pending_downloads == 0:
res = json.loads(data_request)
self.max_obs = res["count"]
self.total_pages = int(self.max_obs/20) + 1
for obs in res["results"]:
new_geom = QgsGeometry.fromPointXY(QgsPointXY(obs["decimalLongitude"], obs["decimalLatitude"]))
new_feature = QgsFeature(self.layer.fields())
new_feature.setGeometry(new_geom)
new_feature.setAttribute(0, str(obs["key"]))
new_feature.setAttribute(1, 0)
new_feature.setAttribute(2, obs["taxonRank"])
if obs["taxonRank"].lower() in ['kingdom', 'phylum', 'order', 'family', 'genus', 'species']:
new_feature.setAttribute(3, obs[obs["taxonRank"].lower()])
else:
new_feature.setAttribute(3, obs["scientificName"])
new_feature.setAttribute(4, "")
new_feature.setAttribute(5, "")
new_feature.setAttribute(6, str(obs["eventDate"]))
# new_feature.setAttribute(7, obs["_datasetKey"]["title"])
new_feature.setAttribute(7, obs["datasetName"] if "datasetName" in obs.keys() else None )
new_feature.setAttribute(8, 1)
new_feature.setAttribute(9, "https://www.gbif.org/fr/occurrence/" + str(obs["key"]))
self.new_features.append(new_feature)
if self.nb_request == 10:
time.sleep(10)
if self.pending_pages < self.total_pages:
self._pending_pages += 300
self.nb_request += 1
self.download()
else:
# Add the new feature to the temporary layer
self.layer.startEditing()
self.layer.dataProvider().addFeatures(self.new_features)
self.layer.updateExtents()
self.layer.commitChanges()
self.layer.triggerRepaint()
GetTaxrefId(self.network_manager, self.project, self.layer, "nom", "niveau")
self.finished_dl.emit()
+173
View File
@@ -0,0 +1,173 @@
# Standard
import json
# PyQt
from qgis.core import QgsAuthMethodConfig, QgsDataSourceUri
from qgis.PyQt.QtCore import QByteArray, QJsonDocument, QObject, QUrl, pyqtSignal
from qgis.PyQt.QtNetwork import QNetworkReply, QNetworkRequest
from qgis.PyQt.QtWidgets import QAction, QMessageBox, QDialog
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
# from PyQt6.QtCore import Qt
# from PyQt6.QtWidgets import QApplication
# Project
from gn_tools.__about__ import __login_url__
class GetLogin(QDialog):
finished_login = pyqtSignal()
"""
Create a filter, specific to GeoNature ZH API and fetch all the features corresponding.
"""
def __init__(
self,
parent=None,
network_manager=None,
params=None
):
super().__init__()
print("enfin !")
self.domain_url = parent.domain_url
self.network_manager = network_manager
self.auth = parent.dlg.mAuthConfigSelect
self.is_connect = parent.dlg.is_connect
self.params = params
print(self.auth.configId())
# self.zhs = None
# self.json_filter = {}
# self.total_zh = 0
# PyQt5 - Version
QApplication.setOverrideCursor(Qt.WaitCursor)
# PyQt6 - Version
# QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
self._pending_downloads = 0
self.check_password()
@property
def pending_downloads(self):
return self._pending_downloads
def handle_finished(self, reply):
self._pending_downloads -= 1
QApplication.restoreOverrideCursor()
# If reply is an error or reply is a redirection, login is not accepted
print(reply.attribute(
QNetworkRequest.HttpStatusCodeAttribute
))
print('Login : ',reply.error())
self.network_manager.cookieStatus = reply.attribute(
QNetworkRequest.HttpStatusCodeAttribute
)
if (
reply.error() != QNetworkReply.NoError
or self.network_manager.cookieStatus != 200
):
# Error code 299 means the logins are invalid
# Redirection not equal to 200 means a redirection happened
if reply.error() == 0:
self.is_connect.setText("Aie !")
msg = QMessageBox()
msg.warning(
None,
self.tr("Warning"),
self.tr(
"Houston, we've got a problem ... \n\n L'utilisateur ou le mot de passe est invalide !"
), # noqa: E501
)
elif reply.error() == 3:
msg = QMessageBox()
msg.warning(
None,
self.tr("Warning"),
self.tr(
"GéoNature's URL not found, add it to the plugin settings"
), # noqa: E501
)
self.close()
self.parent.iface.showOptionsDialog(
currentPage="mOptionsPage{}".format(__title__)
)
self.parent.create_project_from_gn()
else:
print(f"code: {reply.error()} message: {reply.errorString()}")
else:
self.cookies = self.network_manager.cookieJar()
self.is_connect.setText("Good Job !")
# If the authenfication configuration already exists,
# modify it with the new values
# self.manager_config.setConfigMap(
# {
# "password": self.usr,
# "username": self.pwd,
# }
# )
# self.manager.storeAuthenticationConfig(
# self.manager_config, True
# )
# delete sensitive informations... even if it's in the database now...
self.network_manager.accepted = True
# Add the cookie created with the login to the request
self.network_manager.setCookieJar(self.cookies)
# self.network_manager.setCookiesFromUrl(self.cookies,self.domain_url)
self.close()
if self.pending_downloads == 0:
self.finished_login.emit()
return self.network_manager
def get_param_connect(self):
if self.params:
self.usr = self.params["usr"]
self.pwd = self.params["pwd"]
return True
elif self.auth.configId():
authCfg = self.auth.configId()
quri = QgsDataSourceUri()
quri.setParam("authcfg", authCfg)
quri.setParam("url", self.domain_url)
self.usr = quri.uri().split("user='")[1].split("' ",1)[0]
self.pwd = quri.uri().split("password='")[1].split("' ",1)[0]
return True
else :
return False
def check_password(self):
# Test if the user wrote something
if self.auth.configId() or self.params:
is_connect = self.get_param_connect()
url = QUrl(self.domain_url + __login_url__)
print(self.usr)
data_json = {
"login": (
self.usr
),
"password": (
self.pwd
),
}
print(url)
request = QNetworkRequest(url)
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
document = QJsonDocument(data_json)
print(document.toJson())
reply = self.network_manager.post(request, document.toJson())
reply.finished.connect(lambda: self.handle_finished(reply))
self._pending_downloads += 1
else:
msg = QMessageBox()
msg.warning(
None,
self.tr("Warning"),
self.tr(
"GéoNature's Auth not found, add it to the plugin settings"
), # noqa: E501
)
+88
View File
@@ -0,0 +1,88 @@
#! python3 # noqa: E265
"""
Processing provider module.
"""
# PyQGIS
from qgis.core import QgsProcessingProvider
from qgis.PyQt.QtCore import QCoreApplication
from qgis.PyQt.QtGui import QIcon
# project
from gn_tools.__about__ import (
__icon_path__,
__title__,
__version__,
)
# ############################################################################
# ########## Classes ###############
# ##################################
class GnToolsProvider(QgsProcessingProvider):
"""Processing provider class."""
def loadAlgorithms(self):
"""Loads all algorithms belonging to this provider."""
pass
def id(self) -> str:
"""Unique provider id, used for identifying it. This string should be unique, \
short, character only string, eg "qgis" or "gdal". \
This string should not be localised.
:return: provider ID
:rtype: str
"""
return "gn_tools"
def name(self) -> str:
"""Returns the provider name, which is used to describe the provider
within the GUI. This string should be short (e.g. "Lastools") and localised.
:return: provider name
:rtype: str
"""
return __title__
def longName(self) -> str:
"""Longer version of the provider name, which can include
extra details such as version numbers. E.g. "Lastools LIDAR tools". This string should be localised. The default
implementation returns the same string as name().
:return: provider long name
:rtype: str
"""
return self.tr("{} - Tools".format(__title__))
def icon(self) -> QIcon:
"""QIcon used for your provider inside the Processing toolbox menu.
:return: provider icon
:rtype: QIcon
"""
return QIcon(str(__icon_path__))
def tr(self, message: str) -> str:
"""Get the translation for a string using Qt translation API.
:param message: String for translation.
:type message: str, QString
:returns: Translated version of message.
:rtype: str
"""
# noinspection PyTypeChecker,PyArgumentList,PyCallByClass
return QCoreApplication.translate(self.__class__.__name__, message)
def versionInfo(self) -> str:
"""Version information for the provider, or an empty string if this is not \
applicable (e.g. for inbuilt Processing providers). For plugin based providers, \
this should return the plugin’s version identifier.
:return: version
:rtype: str
"""
return __version__
+469
View File
@@ -0,0 +1,469 @@
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# import requests
# import numpy as np
# import pandas as pd
# import os
# Standard
import json
from qgis.core import QgsGeometry, QgsProject, QgsJsonUtils, QgsVectorLayer
# PyQt
from qgis.PyQt.QtCore import (
QUrl,
pyqtSignal,
QJsonDocument,
Qt,
QTextCodec
)
from qgis.PyQt.QtNetwork import QNetworkReply, QNetworkRequest
from qgis.PyQt.QtWidgets import (
QAction,
QMessageBox,
QDialog,
QApplication
)
from gn_tools.__about__ import __api_cd_sta__
def get_status(lst,con):
sql = """
SELECT
t.cd_nom,
t.cd_ref,
t.regne,
t.phylum,
t.classe,
t.ordre,
t.famille,
t.group1_inpn,
t.group2_inpn,
t.group3_inpn,
t.nom_vern,
t.nom_complet,
t.nom_valide,
t.lb_nom,
--s.*
s.cd_sig,
s.rq_statut,
s.code_statut,
s.cd_type_statut,
s.label_statut,
s.niveau_admin,
s.full_citation,
s.doc_url
FROM taxonomie.taxref t
JOIN taxonomie.v_bdc_status s USING (cd_nom)
WHERE t.cd_nom IN {cd_nom}
;""".format(cd_nom = tuple(lst))
return pd.read_sql_query(sql,con)
def get_type_status(con):
sql = """
SELECT * FROM taxonomie.bdc_statut_type
;"""
return pd.read_sql_query(sql,con)
def get_api_status(api,cd_nom:int):
res = requests.api.get('%s/%i'%(api,cd_nom))
if res.status_code == 200:
return res.json()
else :
raise('Error : %i\tcd_nom : %i'%(res.status_code,cd_nom))
def get_taxon_status(lst,api):
from datetime import datetime as dt
init = dt.now()
st = [get_api_status(api,x) for x in lst] # TOO LONG
print(dt.now()-init)
phylo = {
'cd_ref':[x['cd_ref'] for x in st],
'nom_valide':[x['nom_valide'] if 'nom_valide' in x.keys() else None for x in st],
'nom_vernac':[x['nom_vern'] if 'nom_vern' in x.keys() else None for x in st],
'regne':[x['regne'] if 'regne' in x.keys() else None for x in st],
'group1_inp':[x['group1_inpn'] if 'group1_inpn' in x.keys() else None for x in st],
'group2_inp':[x['group2_inp'] if 'group2_inp' in x.keys() else None for x in st],
'group3_inpn':[x['group3_inpn'] for x in st],
'classe':[x['classe'] if 'classe' in x.keys() else None for x in st],
'ordre':[x['ordre'] if 'ordre' in x.keys() else None for x in st],
'famille':[x['famille'] if 'famille' in x.keys() else None for x in st]}
cd_status = {
'AL':[
[val['values'][v]['code_statut']
for val in x['status']['AL']['text'].values() for v in val['values'] ]
if 'AL' in x['status'].keys() else None
for x in st
],
'BERN':[
[val['values'][v]['code_statut']
for val in x['status']['BERN']['text'].values() for v in val['values'] ]
if 'BERN' in x['status'].keys() else None
for x in st
],
'BONN':[
[val['values'][v]['code_statut']
for val in x['status']['BONN']['text'].values() for v in val['values'] ]
if 'BONN' in x['status'].keys() else None
for x in st
],
'DH':[
[val['values'][v]['code_statut']
for val in x['status']['DH']['text'].values() for v in val['values'] ]
if 'DH' in x['status'].keys() else None
for x in st
],
'DO':[
[val['values'][v]['code_statut']
for val in x['status']['DO']['text'].values() for v in val['values'] ]
if 'DO' in x['status'].keys() else None
for x in st
],
'LRE':[
[val['values'][v]['code_statut']
for val in x['status']['LRE']['text'].values() for v in val['values'] ]
if 'LRE' in x['status'].keys() else None
for x in st
],
'LRM':[
[val['values'][v]['code_statut']
for val in x['status']['LRM']['text'].values() for v in val['values'] ]
if 'LRM' in x['status'].keys() else None
for x in st
],
'LRN':[
[val['values'][v]['code_statut']
for val in x['status']['LRN']['text'].values() for v in val['values'] ]
if 'LRN' in x['status'].keys() else None
for x in st
],
'LRR':[
[val['values'][v]['code_statut']
for val in x['status']['LRR']['text'].values() for v in val['values'] ]
if 'LRR' in x['status'].keys() else None
for x in st
],
'PAPNAT':[
[val['values'][v]['code_statut']
for val in x['status']['PAPNAT']['text'].values() for v in val['values'] ]
if 'PAPNAT' in x['status'].keys() else None
for x in st
],
'PD':[
[val['values'][v]['code_statut']
for val in x['status']['PD']['text'].values() for v in val['values'] ]
if 'PD' in x['status'].keys() else None
for x in st
],
'PNA':[
[val['values'][v]['code_statut']
for val in x['status']['PNA']['text'].values() for v in val['values'] ]
if 'PNA' in x['status'].keys() else None
for x in st
],
'PR':[
[val['values'][v]['code_statut']
for val in x['status']['PR']['text'].values() for v in val['values'] ]
if 'PR' in x['status'].keys() else None
for x in st
],
'REGL':[
[val['values'][v]['code_statut']
for val in x['status']['REGL']['text'].values() for v in val['values'] ]
if 'REGL' in x['status'].keys() else None
for x in st
],
'REGLII':[
[val['values'][v]['code_statut']
for val in x['status']['REGLII']['text'].values() for v in val['values'] ]
if 'REGLII' in x['status'].keys() else None
for x in st
],
'REGLLUTTE':[
[val['values'][v]['code_statut']
for val in x['status']['REGLLUTTE']['text'].values() for v in val['values'] ]
if 'REGLLUTTE' in x['status'].keys() else None
for x in st
],
'REGLSO':[
[val['values'][v]['code_statut']
for val in x['status']['REGLSO']['text'].values() for v in val['values'] ]
if 'REGLSO' in x['status'].keys() else None
for x in st
],
'SCAP NAT':[
[val['values'][v]['code_statut']
for val in x['status']['SCAP NAT']['text'].values() for v in val['values'] ]
if 'SCAP NAT' in x['status'].keys() else None
for x in st
],
'SCAP REG':[
[val['values'][v]['code_statut']
for val in x['status']['SCAP REG']['text'].values() for v in val['values'] ]
if 'SCAP REG' in x['status'].keys() else None
for x in st
],
'SENSNAT':[
[val['values'][v]['code_statut']
for val in x['status']['SENSNAT']['text'].values() for v in val['values'] ]
if 'SENSNAT' in x['status'].keys() else None
for x in st
],
'ZDET':[
[val['values'][v]['code_statut']
for val in x['status']['ZDET']['text'].values() for v in val['values'] ]
if 'ZDET' in x['status'].keys() else None
for x in st
],
'exPNA':[
[val['values'][v]['code_statut']
for val in x['status']['exPNA']['text'].values() for v in val['values'] ]
if 'exPNA' in x['status'].keys() else None
for x in st
]
}
return pd.DataFrame({**phylo,**cd_status})
def filter_bio_geo(df,zone_bio):
idNotBioGeo = []
# Filtre du dommaine biogeographique sur la plaine Rhodanienne et Alpine
test_rhod = df.rq_statut.str.contains('rhodanienne : Non déterminante')
test_alpi = df.rq_statut.str.contains('Alpine : Non déterminante')
if zone_bio == 'rhod':
idNotBioGeo = df[test_rhod].index
elif zone_bio == 'alpi':
idNotBioGeo = df[test_alpi].index
elif zone_bio == 'all':
idNotBioGeo = df[test_rhod&test_alpi].index
if not idNotBioGeo.empty:
df.drop(idNotBioGeo, inplace=True)
return df
def form_territoire(df,terr):
is_dep = df.cd_sig.str.contains('INSEED')
is_38 = df.cd_sig=='INSEED38'
if terr == 'isere':
filter_id = df[is_dep&(~is_38)].index
df.drop(filter_id,inplace=True)
elif terr == 'platiere':
lst_stat = df[is_dep&(~is_38)].cd_type_statut.unique()
# is_lst = df.cd_type_statut.isin(lst_stat)
df.loc[is_dep&(~is_38),'cd_type_statut'] = ( df[is_dep&(~is_38)]
.cd_type_statut + '_' + df[is_dep&(~is_38)].cd_sig.str.strip('INSEED')
)
df.loc[is_38,'cd_type_statut'] = ( df[is_38]
.cd_type_statut + '_38'
)
return df
dict_dep = {
'38':'Isère',
'42':'Loire',
'07':'Ardèche',
'26':'Drôme',
}
class PivotStatus(QDialog):
finished_dl = pyqtSignal()
"""
Create a filter, specific to GeoNature ZH API and fetch all the features corresponding.
"""
def __init__(
self,
parent=None,
network_manager=None,
layer=None,
cd_nom=None
):
super().__init__()
print(parent.dlg.tab)
self.dlg = parent.dlg
self.src_name = parent.src_name
self.domain_url = parent.domain_url
self.network_manager = network_manager
self.layer = layer
self.cd_nom = cd_nom
self.url_api = self.domain_url + __api_cd_sta__
# QApplication.setOverrideCursor(Qt.WaitCursor)
self.multi_dep = True
self.cookie = (self.network_manager.cookieJar()
.cookiesForUrl(QUrl(self.domain_url))[0]
)
self._pending_downloads = 0
self.import_status()
@property
def pending_downloads(self):
return self._pending_downloads
def import_status(self):
idx = self.layer.fields().indexOf(self.cd_nom)
unique_cd = self.layer.uniqueValues(idx)
print(len(unique_cd))
self.res = {}
self.get_status(unique_cd)
def get_status(self,cd_nom):
self._pending_downloads += 1
url = QUrl(
self.url_api
+ '?fields=status&cd_nom='
+ ','.join(map(str,cd_nom))
)
request = QNetworkRequest(url)
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
request.setHeader(QNetworkRequest.CookieHeader, self.cookie)
reply = self.network_manager.get(request)
reply.finished.connect(lambda: self.import_finished(reply))
def import_finished(self, reply):
self._pending_downloads -= 1
print(reply.attribute(QNetworkRequest.HttpStatusCodeAttribute))
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
print(f"code: {reply.error()} message: {reply.errorString()}")
else:
data_request = reply.readAll().data().decode()
data = json.loads(data_request)
self.layer_type = self.layer.wkbType().name
self.layer_crs = self.layer.crs()
bdc = self.v_bdc_status(data)
bdc_cd = self._v_bdc_(data,'code')
bdc_lab = self._v_bdc_(data,'label')
self.add_new_layer(bdc)
self.add_new_layer(bdc_cd)
self.add_new_layer(bdc_lab)
# remak_data = {
# "features" :[
# {
# "geometry":
# json.loads(
# QgsGeometry.unaryUnion([
# g.geometry() for g in self.layer.getFeatures()
# if g.attribute(name_cd_nom) == i['cd_ref']
# ]).asJson()
# ),
# "properties" : i ,
# "type": "Feature"
# }
# for i in data['items']
# ],
# "type": "FeatureCollection",
# "geom_type": "Multi"+layer_type
# }
# fcString = json.dumps(remak_data)
# codec = QTextCodec.codecForName("UTF-8")
# fields = QgsJsonUtils.stringToFields(fcString, codec)
# feats = QgsJsonUtils.stringToFeatureList(fcString, fields, codec)
# vl = QgsVectorLayer(remak_data['geom_type'], lay_status_name, "memory")
# if vl.sourceCrs() != layer_crs:
# print('Changed crs !')
# # crs = vl.crs()
# # crs.createFromId(self.api['geometry_srid'])
# vl.setCrs(layer_crs)
# dp = vl.dataProvider()
# dp.addAttributes(fields)
# vl.updateFields()
# dp.addFeatures(feats)
# vl.updateExtents()
if self.pending_downloads == 0:
self.finished_dl.emit()
QApplication.restoreOverrideCursor()
def v_bdc_status(self,data):
return {
"features" :[
{
"properties" : {**{
ii:i[ii]
for ii in i if ii != 'status'
},
**ii
},
"type": "Feature"
}
for i in data['items']
for ii in i['status']
],
"type": "FeatureCollection",
"geom_type": "No geometry",
"layer_name": 'Status - ' + self.layer.name()
}
def _v_bdc_(self,data,type_status):
dtype = 'code_statut' if type_status=='code' else 'label_statut'
return {
"features" :[
{
"geometry": json.loads(
QgsGeometry.unaryUnion([
g.geometry() for g in self.layer.getFeatures()
if g.attribute(self.cd_nom) == i['cd_ref']
]).asJson()
),
"properties" : {
**{
ii:i[ii]
for ii in i if ii != 'status'
}, **{
ii['cd_type_statut'] + '_' + ii['cd_sig'].strip('INSEED')
if self.multi_dep and 'INSEED' in ii['cd_sig']
else ii['cd_type_statut'] : ii[dtype]
for ii in i['status']
}
},
"type": "Feature"
}
for i in data['items']
],
"type": "FeatureCollection",
"geom_type": self.layer_type if "Multi" in self.layer_type else "Multi" + self.layer_type,
"layer_name": 'Status %s - '%type_status + self.layer.name()
}
def add_new_layer(self,data):
geom_type = data['geom_type']
layername = data['layer_name']
fcString = json.dumps(data)
codec = QTextCodec.codecForName("UTF-8")
fields = QgsJsonUtils.stringToFields(fcString, codec)
feats = QgsJsonUtils.stringToFeatureList(fcString, fields, codec)
vl = QgsVectorLayer(geom_type, layername, "memory")
if vl.sourceCrs() != self.layer_crs:
print('Changed crs !')
# crs = vl.crs()
# crs.createFromId(self.api['geometry_srid'])
vl.setCrs(self.layer_crs)
dp = vl.dataProvider()
dp.addAttributes(fields)
vl.updateFields()
dp.addFeatures(feats)
vl.updateExtents()
QgsProject.instance().addMapLayer(vl)
+441
View File
@@ -0,0 +1,441 @@
# Standard
import json
# PyQt
from qgis.core import (
QgsAuthMethodConfig,
QgsDataSourceUri,QgsVectorLayer,
QgsJsonUtils,
QgsProject,
QgsCoordinateReferenceSystem,
QgsCoordinateTransform
)
from qgis.PyQt.QtCore import (
QByteArray,
QJsonDocument,
QObject,
QUrl,
pyqtSignal,
QTextCodec,
Qt
)
from qgis.PyQt.QtNetwork import QNetworkReply, QNetworkRequest
from qgis.PyQt.QtWidgets import QAction, QMessageBox, QDialog, QApplication
# from PyQt5.QtCore import Qt
# from PyQt5.QtWidgets import QApplication
# from PyQt6.QtCore import Qt
# from PyQt6.QtWidgets import QApplication
# Project
from gn_tools.__about__ import (
__api_sn_web__,
__api_sn_exp__,
__api_lst_ex__,
__exept_export__,
)
# from gn_tools.processing import ImportDataGbif
from .gbif import ImportDataGbif
dict_exp = []
class ParamWidget(QDialog):
finished_dl = pyqtSignal()
"""
Create a filter, specific to GeoNature ZH API and fetch all the features corresponding.
"""
def __init__(
self,
parent=None,
network_manager=None
):
super().__init__()
print(parent.dlg.tab)
self.dlg = parent.dlg
self.src_name = parent.src_name
self.domain_url = parent.domain_url
self.network_manager = network_manager
# self.zhs = None
# self.json_filter = {}
# self.total_zh = 0
self._pending_downloads = 0
if self.src_name == 'GBIF':
self.dlg.source_import.addItems(['Synthèse GBIF'])
self.dlg.source_import.currentTextChanged.connect(
self.print_change
)
else :
QApplication.setOverrideCursor(Qt.WaitCursor)
self.cookie = (network_manager.cookieJar()
.cookiesForUrl(QUrl(self.domain_url))[0]
)
self.send_tab_import()
@property
def pending_downloads(self):
return self._pending_downloads
def send_tab_import(self):
if self.src_name == 'GBIF':
self.dlg.source_import.addItems(['Synthèse GBIF'])
self.dlg.source_import.currentTextChanged.connect(
self.print_change
)
else :
self.dlg.source_import.clear()
request = self.get_api(api=__api_lst_ex__)
reply = self.network_manager.get(request)
reply.finished.connect(lambda: self.import_finished(reply))
# reply_exp
# dict_exp = {i for i in exp_all if exp_all[i].geometry_srid and 'sinp' not in exp_all[i].label.lower}
# lst_exp = [i.label for i in dict_exp]
# # self.dlg.source_import.setText(self.tr("Choose source data"))
# self.dlg.source_import.addItems(lst_exp)
# self.dlg.source_import.currentTextChanged.connect(
# self.print_change
# )
def import_finished(self, reply):
self._pending_downloads -= 1
QApplication.restoreOverrideCursor()
print(reply.attribute(QNetworkRequest.HttpStatusCodeAttribute))
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
print(f"code: {reply.error()} message: {reply.errorString()}")
else:
self.lst_exp = ['Synthèse taxons']
if self.src_name == 'GéoNature':
data_request = reply.readAll().data().decode()
data = json.loads(data_request)
items = data['items']
self.dlg.dict_exp = [i for i in items if i['geometry_srid'] ]
if len(__exept_export__) > 0:
for j in __exept_export__ :
self.dlg.dict_exp = [i for i in self.dlg.dict_exp if j not in i['label'].lower()]
self.lst_exp += [i['label'] for i in self.dlg.dict_exp]
self.dlg.source_import.addItems(self.lst_exp)
self.dlg.source_import.currentTextChanged.connect(
self.print_change
)
if self.pending_downloads == 0:
self.finished_dl.emit()
def print_change(self):
print(self.dlg.source_import.currentIndex())
def get_api(self,api=None,apiFilter=None):
self._pending_downloads += 1
# Function launched two times, one to check zh number the other to dl them
url_api = self.domain_url
if api:
url_api += str(api)
url = QUrl(url_api)
print(url)
# Define request properties (cookie, header)
request = QNetworkRequest(url)
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
request.setHeader(QNetworkRequest.CookieHeader, self.cookie)
if apiFilter:
doc = QJsonDocument(self.json_filter)
else:
doc = QJsonDocument({})
return request
# data_request = reply.readAll().data().decode()
# print('tom')
# print(data_request)
# return json.loads(data_request)
# print(data)
class LoadingImport(QDialog):
finished_dl = pyqtSignal()
"""
Create a filter, specific to GeoNature ZH API and fetch all the features corresponding.
"""
def __init__(
self,
parent=None,
network_manager=None
):
super().__init__()
print(parent.dlg.tab)
self.dlg = parent.dlg
self.syn_params = parent.syn_params
self.source = self.dlg.source_import.currentText()
self.src_name = parent.src_name
self.domain_url = parent.domain_url
self.network_manager = network_manager
self.cookie = (network_manager.cookieJar()
.cookiesForUrl(QUrl(self.domain_url))[0]
) if self.src_name != 'GBIF' else None
self.rectangle = parent.rectangle
self.syn_params = parent.syn_params
self.project = parent.project
# self.zhs = None
# self.json_filter = {}
# self.total_zh = 0
QApplication.setOverrideCursor(Qt.WaitCursor)
self._pending_downloads = 0
self.run()
@property
def pending_downloads(self):
return self._pending_downloads
def run(self,items=None):
self._pending_downloads += 1
self.api = None
if self.source == 'Synthèse taxons':
url_api = self.domain_url + __api_sn_web__
self.transform_bouds(4326)
url_api += '?limit=800000'
if self.src_name == 'GéoNature':
geo_param = {
"geoIntersection": {
"type": "Feature",
"properties": {},
"geometry": json.loads(self.rectangle.asJson())
}
} if self.rectangle else {}
elif self.src_name == "Biodiv'AuRA":
geo_param = {
"geoIntersection": self.rectangle.asWkt(),
"with_areas":False
} if self.rectangle else {}
json_param = {
"modif_since_validation": False,
**geo_param,
**self.syn_params
}
doc = QJsonDocument(
json_param
)
print(doc.toJson())
elif self.source == 'Synthèse taxons export':
url_api = self.domain_url + __api_sn_exp__
url_api += '?export_format=geojson'
if self.src_name == 'GéoNature':
lst_id_synth = [
i['properties']['id_synthese']
for i in items['features']
]
elif self.src_name == "Biodiv'AuRA":
lst_id_synth = [
j['id']
for i in items['features']
for j in i['properties']['observations']
]
doc = QJsonDocument(lst_id_synth)
elif self.source == 'Synthèse GBIF':
ImportDataGbif(
self.network_manager, self.project, self.rectangle
)
# self.close()
return QApplication.restoreOverrideCursor()
# self.cookie = None
# url_api = self.domain_url
# url_api += '?advanced=false&geometry={polygon}&offset={offset}'.format(
# offset=1, polygon=self.rectangle.asWkt())
elif self.source in [i['label'] for i in self.dlg.dict_exp]:
self.api = [i for i in self.dlg.dict_exp if i['label'] == self.source][0]
url_api = self.domain_url + 'api/exports/api/%i'%self.api['id']
self.transform_bouds(self.api['geometry_srid'])
url_api += '?limit=0'
if self.rectangle :
url_api += "&geometry=" + str(self.rectangle.asWkt())
url = QUrl(url_api)
print(url)
# print('crs rect : ',self.rectangle.sourceCrs())
# Define request properties (cookie, header)
request = QNetworkRequest(url)
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
if self.cookie:
request.setHeader(QNetworkRequest.CookieHeader, self.cookie)
if 'Synthèse taxons' in self.source :
reply = self.network_manager.post(request,doc.toJson())
else:
reply = self.network_manager.get(request)
reply.finished.connect(lambda: self.loading_finished(reply))
def loading_finished(self,reply):
self._pending_downloads -= 1
print(reply.attribute(QNetworkRequest.HttpStatusCodeAttribute))
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
print(f"code: {reply.error()} message: {reply.errorString()}")
else:
data_request = reply.readAll().data().decode()
data = json.loads(data_request)
items = data['items'] if 'items' in data.keys() else data
if self.source == 'Synthèse taxons':
self.source = 'Synthèse taxons export'
self.run(items)
return
items_point,items_poly,items_line = self.filter_gn_geom(items)
if items_point['features']:
self.addMapLayer(items_point,items_point['geom_type'])
if items_poly['features']:
self.addMapLayer(items_poly,items_poly['geom_type'])
if items_line['features']:
self.addMapLayer(items_line,items_line['geom_type'])
if self.pending_downloads == 0:
self.finished_dl.emit()
QApplication.restoreOverrideCursor()
print("END")
def filter_gn_geom(self,items):
nb_items = len(items['features'])
print('total_count : ',nb_items)
items_poly = {"features":[],"type": "FeatureCollection","geom_type": "MultiPolygon"}
items_line = {"features":[],"type": "FeatureCollection","geom_type": "MultiLine"}
items_point = {
"features":[i for i in items['features'] if 'point' in i['geometry']['type'].lower()],
"type": "FeatureCollection",
"geom_type": "Point"
}
len_data = len(items_point['features'])
if nb_items> len_data:
items_poly = {
"features":[i for i in items['features'] if 'polygon' in i['geometry']['type'].lower()],
"type": "FeatureCollection",
"geom_type": "MultiPolygon"
}
len_data += len(items_poly['features'])
if nb_items> len_data:
items_line = {
"features":[i for i in items['features'] if 'line' in i['geometry']['type'].lower()],
"type": "FeatureCollection",
"geom_type": "MultiLineString"
}
len_data += len(items_line['features'])
return items_point,items_poly,items_line
# def filter_biodiv_geom(self,items):
# items_point = {
# "features":[ {
# 'geometry' : i['geometry'],
# 'properties': j
# }
# for i in items['features'] if 'point' in i['geometry']['type'].lower()
# for j in i['properties']['observations']
# ],
# "type": "FeatureCollection",
# "geom_type": "Point"
# }
# items_poly = {
# "features":[ {
# 'geometry' : i['geometry'],
# 'properties': j
# }
# for i in items['features'] if 'polygon' in i['geometry']['type'].lower()
# for j in i['properties']['observations']
# ],
# "type": "FeatureCollection",
# "geom_type": "MultiPolygon"
# }
# items_line = {
# "features":[ {
# 'geometry' : i['geometry'],
# 'properties': j
# }
# for i in items['features'] if 'line' in i['geometry']['type'].lower()
# for j in i['properties']['observations']
# ],
# "type": "FeatureCollection",
# "geom_type": "MultiLineString"
# }
# return items_point,items_poly,items_line
def addMapLayer(self,data,geom_type):
mon_layer = ' - '.join([self.src_name[:6], self.source])
fcString = json.dumps(data)
codec = QTextCodec.codecForName("UTF-8")
fields = QgsJsonUtils.stringToFields(fcString, codec)
feats = QgsJsonUtils.stringToFeatureList(fcString, fields, codec)
if self.api:
crs_api = QgsCoordinateReferenceSystem(self.api['geometry_srid'])
else :
crs_api = QgsCoordinateReferenceSystem(4326)
vl = QgsVectorLayer(geom_type, mon_layer, "memory")
if vl.sourceCrs() != crs_api:
print('Changed crs !')
# crs = vl.crs()
# crs.createFromId(self.api['geometry_srid'])
vl.setCrs(crs_api)
dp = vl.dataProvider()
dp.addAttributes(fields)
vl.updateFields()
dp.addFeatures(feats)
vl.updateExtents()
if dp.errors():
msg = QMessageBox()
msg.warning(
None,
self.tr("Warning"),
self.tr(
"Houston, we've got a problem ... \n\n %s !"%str(dp.errors())
), # noqa: E501
)
QgsProject.instance().addMapLayer(vl)
def transform_bouds(self,crs):
sourceCrs = QgsCoordinateReferenceSystem(4326)
destCrs = QgsCoordinateReferenceSystem(crs)
if sourceCrs != destCrs:
tr = QgsCoordinateTransform(sourceCrs, destCrs, self.project)
self.rectangle.transform(tr)
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="fr" sourcelanguage="en">
</TS>
@@ -0,0 +1,13 @@
# This file is auto-generated by the script generate_translation_profile.py
# Do not edit this file manually
FORMS = ../../gui/dlg_settings.ui
SOURCES = ../../gui/dlg_settings.py \
../../plugin_main.py \
../../processing/provider.py \
../../toolbelt/env_var_parser.py \
../../toolbelt/log_handler.py \
../../toolbelt/preferences.py
TRANSLATIONS = gn_tools_en.ts
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+3
View File
@@ -0,0 +1,3 @@
#! python3 # noqa: E265
from .log_handler import PlgLogger # noqa: F401
from .preferences import PlgOptionsManager # noqa: F401
+60
View File
@@ -0,0 +1,60 @@
import os
from typing import Type, TypeVar
T = TypeVar("T")
class EnvVarParser:
"""Utility class to retrieve and convert environment variables."""
@staticmethod
def get_env_var(name: str, default: T) -> T:
"""Retrieves an environment variable and converts it based on the default value type.
Args:
name (str): The environment variable name.
default (T): The default value, used to infer the expected type.
Returns:
T: The converted value, matching the type of `default`.
"""
value = os.getenv(name)
if value is None:
return (
default # Return the default value if the environment variable is not
)
# Otherwise, treat it as a single value
return EnvVarParser._convert_single(value, type(default), default)
@staticmethod
def _convert_single(value: str, expected_type: Type[T], default: T) -> T:
"""Converts a string into a single value of the expected type."""
try:
if expected_type is int:
return int(value)
elif expected_type is float:
return float(value)
elif expected_type is bool:
return EnvVarParser._convert_bool(value, default)
elif expected_type is str:
return value # String value
except ValueError:
return default # Return default value in case of conversion failure
raise TypeError(
f"Unsupported type: {expected_type}. Value definition from environment variable is not possible."
)
@staticmethod
def _convert_bool(value: str, default: bool) -> bool:
"""Converts a string into a boolean, handling explicit True/False values."""
true_values = {"1", "true", "yes", "on"}
false_values = {"0", "false", "no", "off"}
value_lower = value.lower()
if value_lower in true_values:
return True
elif value_lower in false_values:
return False
return default # Return default value if conversion fails
+193
View File
@@ -0,0 +1,193 @@
#! python3 # noqa: E265
# standard library
import logging
from functools import partial
from typing import Callable, Literal, Optional, Union
# PyQGIS
from qgis.core import Qgis, QgsMessageLog, QgsMessageOutput
from qgis.gui import QgsMessageBar
from qgis.PyQt.QtWidgets import QPushButton, QWidget
from qgis.utils import iface
# project package
import gn_tools.toolbelt.preferences as plg_prefs_hdlr
from gn_tools.__about__ import __title__
# ############################################################################
# ########## Classes ###############
# ##################################
class PlgLogger(logging.Handler):
"""Python logging handler supercharged with QGIS useful methods."""
@staticmethod
def log(
message: str,
application: str = __title__,
log_level: Union[
Qgis.MessageLevel, Literal[0, 1, 2, 3, 4]
] = Qgis.MessageLevel.Info,
push: bool = False,
duration: Optional[int] = None,
# widget
button: bool = False,
button_text: Optional[str] = None,
button_more_text: Optional[str] = None,
button_connect: Optional[Callable] = None,
# parent
parent_location: Optional[QWidget] = None,
):
"""Send messages to QGIS messages windows and to the user as a message bar. \
Plugin name is used as title. If debug mode is disabled, only warnings (1) and \
errors (2) or with push are sent.
:param message: message to display
:type message: str
:param application: name of the application sending the message. \
Defaults to __about__.__title__
:type application: str, optional
:param log_level: message level. Possible values: any values of enum \
`Qgis.MessageLevel`. For legacy purposes, it's also possible to pass \
corresponding integers but it's not recommended anymore. Legacy values: \
0 (info), 1 (warning), 2 (critical), 3 (success), 4 (none - grey). Defaults \
to Qgis.MessageLevel(0) (info)
:type log_level: Union[Qgis.MessageLevel, Literal[0, 1, 2, 3, 4]], optional
:param push: also display the message in the QGIS message bar in addition to \
the log, defaults to False
:type push: bool, optional
:param duration: duration of the message in seconds. If not set, the \
duration is calculated from the log level: `(log_level + 1) * 3`. seconds. \
If set to 0, then the message must be manually dismissed by the user. \
Defaults to None.
:type duration: int, optional
:param button: display a button in the message bar. Defaults to False.
:type button: bool, optional
:param button_text: text label of the button. Defaults to None.
:type button_text: str, optional
:param button_more_text: text to display within the QgsMessageOutput
:type button_more_text: str, optional
:param button_connect: function to be called when the button is pressed. \
If not set, a simple dialog (QgsMessageOutput) is used to dislay the message. \
Defaults to None.
:type button_connect: Callable, optional
:param parent_location: parent location widget. \
If not set, QGIS canvas message bar is used to push message, \
otherwise if a QgsMessageBar is available in parent_location it is used instead. \
Defaults to None.
:type parent_location: Widget, optional
:Example:
.. code-block:: python
# using enums from Qgis:
# Qgis.Info, Qgis.MessageLevel.Warning, Qgis.MessageLevel.Critical, Qgis.MessageLevel.Success, Qgis.MessageLevel.NoLevel
from qgis.core import Qgis
log(message="Plugin loaded - INFO", log_level=Qgis.MessageLevel.Info, push=False)
log(
message="Something went wrong but it's not blocking",
log_level=Qgis.MessageLevel.Warning
)
log(
message="Plugin failed to load - CRITICAL",
log_level=Qgis.MessageLevel(2),
push=True
)
# LEGACY - using integers:
log(message="Plugin loaded - INFO", log_level=Qgis.MessageLevel.Info, push=False)
log(message="Plugin loaded - WARNING", log_level=Qgis.MessageLevel.Warning, push=1, duration=5)
log(message="Plugin loaded - ERROR", log_level=Qgis.MessageLevel.Critical, push=1, duration=0)
log(
message="Plugin loaded - SUCCESS",
log_level=Qgis.MessageLevel.Success,
push=1,
duration=10,
button=True
)
log(
message="Plugin loaded",
log_level=Qgis.MessageLevel.Critical,
push=1,
duration=0
button=True,
button_label=self.tr("See details"),
button_more_text=detailed_error_message
)
log(message="Plugin loaded - TEST", log_level=Qgis.MessageLevel.NoLevel, push=0)
"""
# if not debug mode and not push, let's ignore INFO, SUCCESS and TEST
debug_mode = plg_prefs_hdlr.PlgOptionsManager.get_plg_settings().debug_mode
if not debug_mode and not push and (log_level < 1 or log_level > 2):
return
# if log_level is an int, convert it to Qgis.MessageLevel
if isinstance(log_level, int):
log_level = Qgis.MessageLevel(log_level)
# ensure message is a string
if not isinstance(message, str):
try:
message = str(message)
except Exception as err:
err_msg = "Log message must be a string, not: {}. Trace: {}".format(
type(message), err
)
logging.error(err_msg)
message = err_msg
# send it to QGIS messages panel
QgsMessageLog.logMessage(
message=message, tag=application, notifyUser=push, level=log_level
)
# optionally, display message on QGIS Message bar (above the map canvas)
if push and iface is not None:
msg_bar = None
# QGIS or custom dialog
if parent_location and isinstance(parent_location, QWidget):
msg_bar = parent_location.findChild(QgsMessageBar)
if not msg_bar:
msg_bar = iface.messageBar()
# calc duration
if duration is None:
duration = (log_level + 1) * 3
# create message with/out a widget
if button:
# create output message
notification = iface.messageBar().createMessage(
title=application, text=message
)
widget_button = QPushButton(button_text or "More...")
if button_connect:
widget_button.clicked.connect(button_connect)
else:
mini_dlg: QgsMessageOutput = QgsMessageOutput.createMessageOutput()
mini_dlg.setTitle(application)
mini_dlg.setMessage(
f"{message}\n{button_more_text}",
QgsMessageOutput.MessageType.MessageText,
)
widget_button.clicked.connect(partial(mini_dlg.showMessage, False))
notification.layout().addWidget(widget_button)
msg_bar.pushWidget(
widget=notification, level=log_level, duration=duration
)
else:
# send simple message
msg_bar.pushMessage(
title=application,
text=message,
level=log_level,
duration=duration,
)
+184
View File
@@ -0,0 +1,184 @@
#! python3 # noqa: E265
"""
Plugin settings.
"""
# standard
from dataclasses import asdict, dataclass, fields
# PyQGIS
from qgis.core import Qgis, QgsSettings
# package
import gn_tools.toolbelt.log_handler as log_hdlr
from gn_tools.__about__ import (
__domain_url__,
__title__,
__version__
)
from gn_tools.toolbelt.env_var_parser import EnvVarParser
# ############################################################################
# ########## Classes ###############
# ##################################
PREFIX_ENV_VARIABLE = "QGIS_GN_TOOLS_"
@dataclass
class PlgEnvVariableSettings:
"""Plugin settings from environnement variable"""
def env_variable_used(self, attribute: str, default_from_name: bool = True) -> str:
"""Get environnement variable used for environnement variable settings
:param attribute: attribute to check
:type attribute: str
:param default_from_name: define default environnement value from attribute name PREFIX_ENV_VARIABLE_<upper case attribute>
:type default_from_name: bool
:return: environnement variable used
:rtype: str
"""
settings_env_variable = asdict(self)
env_variable = settings_env_variable.get(attribute, "")
if not env_variable and default_from_name:
env_variable = f"{PREFIX_ENV_VARIABLE}{attribute}".upper()
return env_variable
@dataclass
class PlgSettingsStructure:
"""Plugin settings structure and defaults values."""
# global
debug_mode: bool = False
version: str = __version__
# param
domain_url: str = __domain_url__
class PlgOptionsManager:
@staticmethod
def get_plg_settings() -> PlgSettingsStructure:
"""Load and return plugin settings as a dictionary. \
Useful to get user preferences across plugin logic.
:return: plugin settings
:rtype: PlgSettingsStructure
"""
# get dataclass fields definition
settings_fields = fields(PlgSettingsStructure)
env_variable_settings = PlgEnvVariableSettings()
# retrieve settings from QGIS/Qt
settings = QgsSettings()
settings.beginGroup(__title__)
# map settings values to preferences object
li_settings_values = []
for i in settings_fields:
try:
value = settings.value(key=i.name, defaultValue=i.default, type=i.type)
# If environnement variable used, get value from environnement variable
env_variable = env_variable_settings.env_variable_used(i.name)
if env_variable:
value = EnvVarParser.get_env_var(env_variable, value)
li_settings_values.append(value)
except TypeError:
li_settings_values.append(
settings.value(key=i.name, defaultValue=i.default)
)
# instanciate new settings object
options = PlgSettingsStructure(*li_settings_values)
settings.endGroup()
return options
@staticmethod
def get_value_from_key(key: str, default=None, exp_type=None):
"""Load and return plugin settings as a dictionary. \
Useful to get user preferences across plugin logic.
:return: plugin settings value matching key
"""
if not hasattr(PlgSettingsStructure, key):
log_hdlr.PlgLogger.log(
message="Bad settings key. Must be one of: {}".format(
",".join(PlgSettingsStructure._fields)
),
log_level=Qgis.MessageLevel.Warning,
)
return None
settings = QgsSettings()
settings.beginGroup(__title__)
try:
out_value = settings.value(key=key, defaultValue=default, type=exp_type)
except Exception as err:
log_hdlr.PlgLogger.log(
message="Error occurred trying to get settings: {}.Trace: {}".format(
key, err
)
)
out_value = None
settings.endGroup()
return out_value
@classmethod
def set_value_from_key(cls, key: str, value) -> bool:
"""Set plugin QSettings value using the key.
:param key: QSettings key
:type key: str
:param value: value to set
:type value: depending on the settings
:return: operation status
:rtype: bool
"""
if not hasattr(PlgSettingsStructure, key):
log_hdlr.PlgLogger.log(
message="Bad settings key. Must be one of: {}".format(
",".join(PlgSettingsStructure._fields)
),
log_level=Qgis.MessageLevel.Critical,
)
return False
settings = QgsSettings()
settings.beginGroup(__title__)
try:
settings.setValue(key, value)
out_value = True
except Exception as err:
log_hdlr.PlgLogger.log(
message="Error occurred trying to set settings: {}.Trace: {}".format(
key, err
)
)
out_value = False
settings.endGroup()
return out_value
@classmethod
def save_from_object(cls, plugin_settings_obj: PlgSettingsStructure):
"""Load and return plugin settings as a dictionary. \
Useful to get user preferences across plugin logic.
:return: plugin settings value matching key
"""
settings = QgsSettings()
settings.beginGroup(__title__)
for k, v in asdict(plugin_settings_obj).items():
cls.set_value_from_key(k, v)
settings.endGroup()