Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a5ed12797 | ||
|
|
021747ae06 |
+55
-12
@@ -19,7 +19,7 @@ Designprinzipien
|
||||
|
||||
from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple
|
||||
|
||||
from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse
|
||||
from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse, quote
|
||||
import json
|
||||
import time
|
||||
|
||||
@@ -132,6 +132,7 @@ class Datenabruf:
|
||||
# außer bei WMS (WMS bleibt unverändert).
|
||||
use_bbox = (raumfilter != "ohne") and (str(provider).upper() != "WMS")
|
||||
url = self._build_provider_url(link=link, provider=str(provider), bbox_geom=bbox_geom if use_bbox else None)
|
||||
print(f"[Datenabruf] URL für {thema!r} ({provider}): {url}")
|
||||
|
||||
# 2b) Fachdaten abrufen
|
||||
features, error_msg = self._fetch_features(
|
||||
@@ -309,8 +310,11 @@ class Datenabruf:
|
||||
if callable(extent_to_bbox):
|
||||
bbox_str = extent_to_bbox(extent_obj)
|
||||
else:
|
||||
# Fallback: einfache xmin/ymin/xmax/ymax-Extraktion (duck-typing)
|
||||
if hasattr(extent_obj, "xmin") and callable(getattr(extent_obj, "xmin")):
|
||||
# Fallback: xmin/ymin/xmax/ymax-Extraktion (duck-typing)
|
||||
# QgsRectangle verwendet xMinimum()/yMinimum()/xMaximum()/yMaximum()
|
||||
if hasattr(extent_obj, "xMinimum") and callable(getattr(extent_obj, "xMinimum")):
|
||||
bbox_str = f"{extent_obj.xMinimum()},{extent_obj.yMinimum()},{extent_obj.xMaximum()},{extent_obj.yMaximum()}"
|
||||
elif hasattr(extent_obj, "xmin") and callable(getattr(extent_obj, "xmin")):
|
||||
bbox_str = f"{extent_obj.xmin()},{extent_obj.ymin()},{extent_obj.xmax()},{extent_obj.ymax()}"
|
||||
elif isinstance(extent_obj, (tuple, list)) and len(extent_obj) == 4:
|
||||
bbox_str = f"{extent_obj[0]},{extent_obj[1]},{extent_obj[2]},{extent_obj[3]}"
|
||||
@@ -325,12 +329,26 @@ class Datenabruf:
|
||||
if provider_norm == "WFS":
|
||||
query_params.setdefault("service", "WFS")
|
||||
query_params.setdefault("request", "GetFeature")
|
||||
query_params.setdefault("outputFormat", "application/json")
|
||||
if bbox_str:
|
||||
query_params.setdefault("BBOX", bbox_str)
|
||||
query_params.setdefault("VERSION", "2.0.0")
|
||||
# WFS 2.0: 'typename' → 'TYPENAMES'
|
||||
if "typename" in query_params and "TYPENAMES" not in query_params:
|
||||
query_params["TYPENAMES"] = query_params.pop("typename")
|
||||
# resultType=hits: nur Anzahl vom Server abfragen, kein GML-Download
|
||||
query_params.setdefault("resultType", "hits")
|
||||
# CRS in OGC-URN-Format (wie QGIS Datenquellenverwaltung)
|
||||
srsname_urn: Optional[str] = None
|
||||
if crs_authid:
|
||||
query_params.setdefault("SRSNAME", crs_authid)
|
||||
new_query = urlencode(query_params, doseq=True)
|
||||
if crs_authid.upper().startswith("EPSG:"):
|
||||
srsname_urn = f"urn:ogc:def:crs:EPSG::{crs_authid.split(':', 1)[1]}"
|
||||
else:
|
||||
srsname_urn = crs_authid
|
||||
if bbox_str:
|
||||
# 5. Komponente: CRS-URN anhängen (Standard für WFS 2.0)
|
||||
bbox_with_crs = f"{bbox_str},{srsname_urn}" if srsname_urn else bbox_str
|
||||
query_params.setdefault("BBOX", bbox_with_crs)
|
||||
if srsname_urn:
|
||||
query_params.setdefault("SRSNAME", srsname_urn)
|
||||
new_query = urlencode(query_params, doseq=True, quote_via=quote, safe=':/')
|
||||
rebuilt = parsed._replace(query=new_query)
|
||||
return urlunparse(rebuilt)
|
||||
|
||||
@@ -389,14 +407,14 @@ class Datenabruf:
|
||||
query_params.setdefault("inSR", srid)
|
||||
query_params.setdefault("outSR", srid)
|
||||
|
||||
new_query = urlencode(query_params, doseq=True)
|
||||
new_query = urlencode(query_params, doseq=True, quote_via=quote, safe=':/')
|
||||
rebuilt = parsed_rest._replace(query=new_query)
|
||||
return urlunparse(rebuilt)
|
||||
|
||||
# Default: generischer bbox-Parameter (nur wenn vorhanden)
|
||||
if bbox_str:
|
||||
query_params.setdefault("bbox", bbox_str)
|
||||
new_query = urlencode(query_params, doseq=True)
|
||||
new_query = urlencode(query_params, doseq=True, quote_via=quote, safe=':/')
|
||||
rebuilt = parsed._replace(query=new_query)
|
||||
return urlunparse(rebuilt)
|
||||
|
||||
@@ -573,7 +591,33 @@ class Datenabruf:
|
||||
# Sonst: gib das gesamte JSON als einzelnes Objekt zurück
|
||||
return [parsed], None
|
||||
except json.JSONDecodeError:
|
||||
# Nicht-JSON-Antwort (z. B. GML). Wenn QGIS verfügbar, versuche GML via temporärer Datei + OGR
|
||||
# Nicht-JSON-Antwort (GML/XML). Für WFS resultType=hits: Anzahl aus XML lesen.
|
||||
if prov == "WFS":
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
root = ET.fromstring(response_text)
|
||||
# WFS 2.0: numberOfMatched; WFS 1.x: numberOfFeatures
|
||||
n_str = (
|
||||
root.get("numberOfMatched")
|
||||
or root.get("numberMatched")
|
||||
or root.get("numberOfFeatures")
|
||||
or root.get("numberOfReturned")
|
||||
)
|
||||
if n_str is not None:
|
||||
try:
|
||||
count = int(n_str)
|
||||
return [{}] * count, None
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
# Fallback: wfs:member-Elemente zählen
|
||||
members = [
|
||||
c for c in root
|
||||
if c.tag.lower().endswith(("member", "featuremember"))
|
||||
]
|
||||
return [{}] * len(members), None
|
||||
except Exception as exc:
|
||||
return [], f"WFS-XML-Antwort konnte nicht geparst werden: {exc}"
|
||||
# Nicht-WFS, nicht-JSON: GML via temporärer Datei + OGR
|
||||
if getattr(qgiscore, "QGIS_AVAILABLE", False):
|
||||
try:
|
||||
import tempfile
|
||||
@@ -588,5 +632,4 @@ class Datenabruf:
|
||||
return [], "GML-Antwort konnte nicht als Layer geladen werden"
|
||||
except Exception as exc:
|
||||
return [], f"Fehler beim Parsen von GML: {exc}"
|
||||
# Wenn alles fehlschlägt:
|
||||
return [], "Antwort konnte nicht als JSON oder GML geparst werden"
|
||||
|
||||
+27
-1
@@ -122,7 +122,33 @@ class LayerLoader:
|
||||
|
||||
try:
|
||||
if provider_lower == "wfs":
|
||||
uri = link if link.strip().lower().startswith("url=") else f"url={link}"
|
||||
raw_link = link.strip()
|
||||
if raw_link.lower().startswith("url="):
|
||||
raw_link = raw_link[4:]
|
||||
# QGIS WFS-Provider erwartet die Basis-URL und 'typename' als
|
||||
# separaten URI-Parameter. GetFeature-spezifische Parameter
|
||||
# (request, outputFormat, resultType, BBOX, …) werden entfernt.
|
||||
try:
|
||||
from urllib.parse import urlparse as _up, parse_qsl as _pqsl, urlunparse as _uu, urlencode as _ue
|
||||
_parsed = _up(raw_link)
|
||||
_params = dict(_pqsl(_parsed.query, keep_blank_values=True))
|
||||
_typename = (
|
||||
_params.pop("typename", None)
|
||||
or _params.pop("TYPENAME", None)
|
||||
or _params.pop("TYPENAMES", None)
|
||||
)
|
||||
for _k in ("request", "REQUEST", "outputFormat", "OUTPUTFORMAT",
|
||||
"resultType", "RESULTTYPE", "BBOX", "bbox",
|
||||
"STARTINDEX", "COUNT"):
|
||||
_params.pop(_k, None)
|
||||
_params.setdefault("service", "WFS")
|
||||
_params.setdefault("version", "2.0.0")
|
||||
_base_url = _uu(_parsed._replace(query=_ue(_params)))
|
||||
uri = f"url={_base_url}"
|
||||
if _typename:
|
||||
uri += f" typename={_typename}"
|
||||
except Exception:
|
||||
uri = link if link.strip().lower().startswith("url=") else f"url={link}"
|
||||
layer = QgsVectorLayer(uri, thema, "WFS")
|
||||
elif provider_lower == "wms":
|
||||
uri = link if link.strip().lower().startswith("url=") else f"url={link}"
|
||||
|
||||
Reference in New Issue
Block a user