forked from CEN38/plugin_gn_tools
init plugin
This commit is contained in:
@@ -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__}")
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
@@ -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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Ubuntu Sans'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></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>
|
||||
Executable
+169
@@ -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__
|
||||
@@ -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><html><head/><body><p align="center"><span style=" font-weight:600;">PluginTitle - Version X.X.X</span></p></body></html></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>
|
||||
@@ -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/
|
||||
Executable
+566
@@ -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")
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
)
|
||||
@@ -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__
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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 |
@@ -0,0 +1,3 @@
|
||||
#! python3 # noqa: E265
|
||||
from .log_handler import PlgLogger # noqa: F401
|
||||
from .preferences import PlgOptionsManager # noqa: F401
|
||||
@@ -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
|
||||
Executable
+193
@@ -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,
|
||||
)
|
||||
Executable
+184
@@ -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()
|
||||
Reference in New Issue
Block a user