Compare commits

..
Author SHA1 Message Date
Daniel 12b52d0468 Merge pull request 'proxy-fix' (#16) from feature/grenzpunkte_laden into unstable
Release Plugin / release (push) Successful in 4s
Reviewed-on: #16
2026-05-06 11:49:45 +02:00
Daniel cbd7b84889 Merge branch 'unstable' of https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Plugin_SN_Verfahrensgebiet into feature/grenzpunkte_laden 2026-05-06 11:45:50 +02:00
DanielandCopilot 28cfc75121 proxy-fix
Co-authored-by: Copilot <copilot@github.com>
2026-05-06 11:43:44 +02:00
release-bot cee02ae935 Release 26.5.1-unstable 2026-05-06 08:39:31 +00:00
5 changed files with 92 additions and 58 deletions
+2
View File
@@ -1,4 +1,6 @@
--- ---
Version 26.5.1-unstable:
---
Version 26.4.6-unstable: Version 26.4.6-unstable:
--- ---
Version 26.4.5-unstable: Version 26.4.5-unstable:
+66 -54
View File
@@ -146,6 +146,9 @@ def _wfs_get_gml(url: str, typename: str, extra_params: dict) -> Optional[bytes]
GET-Requests mit ``BBOX=``-KVP-Parameter oder ResourceId-``FILTER`` (nur GET-Requests mit ``BBOX=``-KVP-Parameter oder ResourceId-``FILTER`` (nur
``xmlns:fes``, kein GML) werden dagegen akzeptiert. ``xmlns:fes``, kein GML) werden dagegen akzeptiert.
**Proxy-Unterstützung:** Verwendet ``QgsBlockingNetworkRequest``, das automatisch
die in QGIS konfigurierten Proxy-Einstellungen berücksichtigt.
Parameters Parameters
---------- ----------
url : url :
@@ -161,10 +164,10 @@ def _wfs_get_gml(url: str, typename: str, extra_params: dict) -> Optional[bytes]
bytes | None bytes | None
Rohe GML-Antwort oder ``None`` bei Fehler. Rohe GML-Antwort oder ``None`` bei Fehler.
""" """
import urllib.request
import urllib.parse import urllib.parse
import urllib.error from qgis.core import QgsMessageLog, Qgis, QgsBlockingNetworkRequest
from qgis.core import QgsMessageLog, Qgis from qgis.PyQt.QtNetwork import QNetworkRequest
from qgis.PyQt.QtCore import QUrl
params = { params = {
'SERVICE': 'WFS', 'SERVICE': 'WFS',
@@ -177,26 +180,34 @@ def _wfs_get_gml(url: str, typename: str, extra_params: dict) -> Optional[bytes]
full_url = url + '?' + urllib.parse.urlencode(params) full_url = url + '?' + urllib.parse.urlencode(params)
try: req = QNetworkRequest(QUrl(full_url))
with urllib.request.urlopen(full_url, timeout=120) as resp: req.setTransferTimeout(120_000)
gml_bytes = resp.read() blocker = QgsBlockingNetworkRequest()
except urllib.error.HTTPError as exc: err = blocker.get(req)
body_preview = exc.read(200).decode('utf-8', errors='replace')
if err != QgsBlockingNetworkRequest.ErrorCode.NoError:
msg = blocker.errorMessage()
QgsMessageLog.logMessage( QgsMessageLog.logMessage(
f"WFS GET HTTP {exc.code} ({typename}): {exc.reason}\n" f"WFS GET fehlgeschlagen ({typename}): {msg}\nURL: {full_url[:300]}",
"sn_verfahrensgebiet",
Qgis.MessageLevel.Critical,
)
warning("WFS GET", f"Netzwerk-/Proxy-Fehler: {msg}", duration=10)
return None
reply = blocker.reply()
http_status = reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute)
gml_bytes = bytes(reply.content())
if http_status is not None and http_status >= 400:
body_preview = gml_bytes[:200].decode('utf-8', errors='replace')
QgsMessageLog.logMessage(
f"WFS GET HTTP {http_status} ({typename})\n"
f"URL: {full_url[:300]}\nAntwort: {body_preview}", f"URL: {full_url[:300]}\nAntwort: {body_preview}",
"sn_verfahrensgebiet", "sn_verfahrensgebiet",
Qgis.MessageLevel.Critical, Qgis.MessageLevel.Critical,
) )
warning("WFS GET", f"HTTP {exc.code}: {exc.reason}", duration=10) warning("WFS GET", f"HTTP {http_status}", duration=10)
return None
except Exception as exc:
QgsMessageLog.logMessage(
f"WFS GET fehlgeschlagen ({typename}): {exc}\nURL: {full_url[:300]}",
"sn_verfahrensgebiet",
Qgis.MessageLevel.Critical,
)
warning("WFS GET", f"Netzwerkfehler: {exc}", duration=10)
return None return None
if not gml_bytes: if not gml_bytes:
@@ -477,11 +488,7 @@ def _lade_grenzpunkte_als_dict(
) )
_gml_bytes = _wfs_get_gml(ALKIS_SF_URL, GRENZPUNKT_TYPENAME, {'FILTER': filter_xml}) _gml_bytes = _wfs_get_gml(ALKIS_SF_URL, GRENZPUNKT_TYPENAME, {'FILTER': filter_xml})
if not _gml_bytes: if not _gml_bytes:
warning( return {}, []
"Grenzpunkte (Diagnose)",
f"Batch {i + 1}/{anzahl_batches}: GET-Anfrage fehlgeschlagen – Batch wird übersprungen.",
duration=4,
)
else: else:
tmp = _parse_alkis_gml_memory_layer(_gml_bytes, "AX_Grenzpunkt", "_TmpGP", geom_prop=None) tmp = _parse_alkis_gml_memory_layer(_gml_bytes, "AX_Grenzpunkt", "_TmpGP", geom_prop=None)
if not tmp: if not tmp:
@@ -704,9 +711,12 @@ def _lade_gemarkung_extents_mit_puffer(
return extents return extents
def _wfs_hits_count(url: str, typename: str, extra_params: dict) -> int: def _wfs_hits_count(url: str, typename: str, extra_params: dict) -> Optional[int]:
"""Fragt die Anzahl der Features per ``resultType=hits`` ab. """Fragt die Anzahl der Features per ``resultType=hits`` ab.
**Proxy-Unterstützung:** Verwendet ``QgsBlockingNetworkRequest``, das automatisch
die in QGIS konfigurierten Proxy-Einstellungen berücksichtigt.
Parameters Parameters
---------- ----------
url : url :
@@ -714,17 +724,21 @@ def _wfs_hits_count(url: str, typename: str, extra_params: dict) -> int:
typename : typename :
Vollqualifizierter Feature-Typ. Vollqualifizierter Feature-Typ.
extra_params : extra_params :
Zus\u00e4tzliche KVP-Parameter (z.B. ``{'BBOX': '...'}``). Zusätzliche KVP-Parameter (z.B. ``{'BBOX': '...'}``).
Returns Returns
------- -------
int Optional[int]
Anzahl der Features oder ``-1`` wenn nicht ermittelbar. Anzahl der Features,
``-1`` wenn der Server antwortete aber ``numberMatched='unknown'`` lieferte
(gültiger WFS-Fallback, kein Fehler), oder
``None`` bei Netzwerk-/Proxy-Fehler (Aufrufer soll abbrechen).
""" """
import urllib.request
import urllib.parse import urllib.parse
import urllib.error
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from qgis.core import QgsMessageLog, Qgis, QgsBlockingNetworkRequest
from qgis.PyQt.QtNetwork import QNetworkRequest
from qgis.PyQt.QtCore import QUrl
params = { params = {
'SERVICE': 'WFS', 'SERVICE': 'WFS',
@@ -736,11 +750,22 @@ def _wfs_hits_count(url: str, typename: str, extra_params: dict) -> int:
params.update(extra_params) params.update(extra_params)
full_url = url + '?' + urllib.parse.urlencode(params) full_url = url + '?' + urllib.parse.urlencode(params)
try:
with urllib.request.urlopen(full_url, timeout=30) as resp: req = QNetworkRequest(QUrl(full_url))
gml_bytes = resp.read() req.setTransferTimeout(30_000)
except Exception: blocker = QgsBlockingNetworkRequest()
return -1 err = blocker.get(req)
if err != QgsBlockingNetworkRequest.ErrorCode.NoError:
msg = blocker.errorMessage()
QgsMessageLog.logMessage(
f"WFS Hits-Abfrage fehlgeschlagen ({typename}): {msg}\nURL: {full_url[:300]}",
"sn_verfahrensgebiet",
Qgis.MessageLevel.Critical,
)
return None
gml_bytes = bytes(blocker.reply().content())
try: try:
root = ET.fromstring(gml_bytes) root = ET.fromstring(gml_bytes)
@@ -796,12 +821,7 @@ def _lade_punktort_ta_batches_per_gemarkung(
) )
_gml_bytes = _wfs_get_gml(ALKIS_SF_URL, PUNKTORT_TA_TYPENAME, {'BBOX': _punktort_ta_bbox_param(bbox)}) _gml_bytes = _wfs_get_gml(ALKIS_SF_URL, PUNKTORT_TA_TYPENAME, {'BBOX': _punktort_ta_bbox_param(bbox)})
if not _gml_bytes: if not _gml_bytes:
warning( return None
"Grenzpunkte (Diagnose)",
f"Gemarkung {_gemarkungsnummer}: GET-Anfrage fehlgeschlagen – wird übersprungen.",
duration=4,
)
continue
tmp = _parse_alkis_gml_memory_layer(_gml_bytes, "AX_PunktortTA", "_TmpPTA", geom_prop="position", extract_qualitaet=True) tmp = _parse_alkis_gml_memory_layer(_gml_bytes, "AX_PunktortTA", "_TmpPTA", geom_prop="position", extract_qualitaet=True)
if not tmp: if not tmp:
warning( warning(
@@ -997,6 +1017,8 @@ def lade_punktort_ta(
bbox_param = _punktort_ta_bbox_param(bbox) bbox_param = _punktort_ta_bbox_param(bbox)
n_hits = _wfs_hits_count(ALKIS_SF_URL, PUNKTORT_TA_TYPENAME, {'BBOX': bbox_param}) n_hits = _wfs_hits_count(ALKIS_SF_URL, PUNKTORT_TA_TYPENAME, {'BBOX': bbox_param})
if n_hits is None:
return None
n_batches = max(1, math.ceil(n_hits / _BBOX_BATCH)) if n_hits > 0 else 1 n_batches = max(1, math.ceil(n_hits / _BBOX_BATCH)) if n_hits > 0 else 1
info( info(
@@ -1022,13 +1044,7 @@ def lade_punktort_ta(
_gml_bytes = _wfs_get_gml(ALKIS_SF_URL, PUNKTORT_TA_TYPENAME, extra) _gml_bytes = _wfs_get_gml(ALKIS_SF_URL, PUNKTORT_TA_TYPENAME, extra)
if not _gml_bytes: if not _gml_bytes:
warning( return None
"Grenzpunkte (Diagnose)",
f"Gemarkung {gemarkungsnummer} Batch {b_idx + 1}: "
f"GET-Anfrage fehlgeschlagen – übersprungen.",
duration=4,
)
continue
tmp = _parse_alkis_gml_memory_layer( tmp = _parse_alkis_gml_memory_layer(
_gml_bytes, "AX_PunktortTA", "_TmpPTA", geom_prop="position", extract_qualitaet=True _gml_bytes, "AX_PunktortTA", "_TmpPTA", geom_prop="position", extract_qualitaet=True
@@ -1343,6 +1359,8 @@ def grenzpunkte_gemarkungsweise(
for gemarkungsnummer, bbox in extents.items(): for gemarkungsnummer, bbox in extents.items():
bbox_param = _punktort_ta_bbox_param(bbox) bbox_param = _punktort_ta_bbox_param(bbox)
n_hits = _wfs_hits_count(ALKIS_SF_URL, PUNKTORT_TA_TYPENAME, {'BBOX': bbox_param}) n_hits = _wfs_hits_count(ALKIS_SF_URL, PUNKTORT_TA_TYPENAME, {'BBOX': bbox_param})
if n_hits is None:
return None
hits_pro_gemarkung[gemarkungsnummer] = n_hits hits_pro_gemarkung[gemarkungsnummer] = n_hits
batches = max(1, math.ceil(n_hits / _BBOX_BATCH)) if n_hits > 0 else 1 batches = max(1, math.ceil(n_hits / _BBOX_BATCH)) if n_hits > 0 else 1
n_pta_batches_gesamt += batches n_pta_batches_gesamt += batches
@@ -1413,13 +1431,7 @@ def grenzpunkte_gemarkungsweise(
_gml_bytes = _wfs_get_gml(ALKIS_SF_URL, PUNKTORT_TA_TYPENAME, extra) _gml_bytes = _wfs_get_gml(ALKIS_SF_URL, PUNKTORT_TA_TYPENAME, extra)
if not _gml_bytes: if not _gml_bytes:
warning( return None
"Grenzpunkte (Diagnose)",
f"Gemarkung {gemarkungsnummer} Batch {b_idx + 1}: "
f"GET-Anfrage fehlgeschlagen – Batch wird übersprungen.",
duration=6,
)
continue
tmp = _parse_alkis_gml_memory_layer( tmp = _parse_alkis_gml_memory_layer(
_gml_bytes, "AX_PunktortTA", "_TmpPTA", geom_prop="position", extract_qualitaet=True _gml_bytes, "AX_PunktortTA", "_TmpPTA", geom_prop="position", extract_qualitaet=True
@@ -488,6 +488,17 @@ def verfahrensgebiet_alkis_komplett(
if punktort_layer is None: if punktort_layer is None:
detail_progress.close() detail_progress.close()
abgeschlossen = True abgeschlossen = True
if not detail_progress.is_canceled():
QMessageBox.critical(
tab_widget,
"WFS-Fehler",
"Die Grenzpunktdaten (PunktortTA) konnten nicht geladen werden.\n\n"
"Mögliche Ursache: Netzwerk- oder Proxy-Fehler.\n"
"Bitte prüfen Sie die QGIS-Proxy-Einstellungen "
"(Einstellungen -> Optionen -> Netzwerk) und den Log-Eintrag "
"unter Ansicht -> Bedienfelder -> Protokollnachrichten "
"(Kategorie 'sn_verfahrensgebiet').",
)
return enriched_layer, flst_layer, alkis_vg_layer, vg_status return enriched_layer, flst_layer, alkis_vg_layer, vg_status
# Schritte 2…Ende: Knickpunktprüfung # Schritte 2…Ende: Knickpunktprüfung
+1 -1
View File
@@ -1,5 +1,5 @@
[general] [general]
version=26.4.6-unstable version=26.5.1-unstable
name=LNO Sachsen | Verfahrensgebiet name=LNO Sachsen | Verfahrensgebiet
description=Plugin zum Erzeugen eines Objektes "Verfahrensgebiet" description=Plugin zum Erzeugen eines Objektes "Verfahrensgebiet"
author=Daniel Helbig, Michael Otto author=Daniel Helbig, Michael Otto
+12 -3
View File
@@ -487,10 +487,19 @@ class WorkingTab(QWidget):
if joined_layer and joined_layer.isValid(): if joined_layer and joined_layer.isValid():
iface.mapCanvas().refresh() iface.mapCanvas().refresh()
self.setze_haken(self.haken_grenz, True) self.setze_haken(self.haken_grenz, True)
success( QMessageBox.information(
"Grenzpunkte", self,
"Grenzpunkte geladen",
f"Grenzpunkte erfolgreich geladen ({joined_layer.featureCount()} Objekte).", f"Grenzpunkte erfolgreich geladen ({joined_layer.featureCount()} Objekte).",
duration=5, )
elif joined_layer is None:
error(
"Grenzpunkte",
"Laden fehlgeschlagen – WFS- oder Netzwerkfehler. "
"Details unter Ansicht -> Bedienfelder -> Protokollnachrichten "
"(Kategorie 'sn_verfahrensgebiet'). "
"Bitte prüfen Sie ggf. die Proxy-Einstellungen.",
duration=0,
) )
finally: finally:
self._set_busy(self.btn_grenz_alkis, False, "Grenzpunkte aus ALKIS laden") self._set_busy(self.btn_grenz_alkis, False, "Grenzpunkte aus ALKIS laden")