VLN-API integriert
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
"""Konvertierung GeoJSON <-> QGIS-Layer für die VLN-API.
|
||||
|
||||
Lokale Kopie des Layer-Managers aus dem vln_karten-Plugin — sn_plan41
|
||||
benötigt kein separat installiertes vln_karten-Plugin mehr.
|
||||
|
||||
Die API liefert je Layer (z.B. Plan 41) gemischte Geometrietypen in einer
|
||||
FeatureCollection: Punkte, Linien, Polygone sowie deren Multi-Varianten.
|
||||
QGIS-Memory-Layer können nur einen Geometrietyp halten — beim Laden wird
|
||||
deshalb nach Geometrie-Familie (Punkte/Linien/Flächen) in bis zu drei
|
||||
Layer gesplittet; Single-Geometrien werden zu Multi befördert.
|
||||
|
||||
Beim Hochladen müssen alle Teil-Layer desselben Datensatzes wieder zu
|
||||
EINER FeatureCollection vereint werden, weil der PUT der API den
|
||||
kompletten Layer-Bestand der VKZ ersetzt.
|
||||
|
||||
Custom-Properties an Layern (kompatibel mit vln_karten, falls beide
|
||||
Plugins gleichzeitig installiert sind):
|
||||
vln_karten/dataset — z.B. "p41"
|
||||
vln_karten/verfahren — VKZ-Nummer
|
||||
vln_karten/api_path — API-Pfad für Upload
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from sn_basis.functions.qgiscore_wrapper import (
|
||||
QgsJsonExporter,
|
||||
QgsJsonUtils,
|
||||
QgsProject,
|
||||
QgsVectorLayer,
|
||||
GEOM_POINT,
|
||||
GEOM_LINE,
|
||||
GEOM_POLYGON,
|
||||
)
|
||||
|
||||
_GEOM_POINT = GEOM_POINT
|
||||
_GEOM_LINE = GEOM_LINE
|
||||
_GEOM_POLYGON = GEOM_POLYGON
|
||||
|
||||
# Die VLN-API liefert Koordinaten in ETRS89 / UTM Zone 33 (EPSG:25833).
|
||||
GEOJSON_CRS = "EPSG:25833"
|
||||
|
||||
# Custom-Property-Schlüssel — identisch mit vln_karten für Kompatibilität.
|
||||
PROP_DATASET = "vln_karten/dataset"
|
||||
PROP_VERFAHREN = "vln_karten/verfahren"
|
||||
PROP_PATH = "vln_karten/api_path"
|
||||
|
||||
# Geometrie-Familie → (Memory-Provider-Typ, Namenszusatz).
|
||||
# Reihenfolge = Lade-Reihenfolge: Flächen zuerst, damit Punkte im
|
||||
# Layerbaum oben landen und nicht verdeckt werden.
|
||||
_FAMILIES = (
|
||||
("polygon", "MultiPolygon", "Flächen"),
|
||||
("line", "MultiLineString", "Linien"),
|
||||
("point", "MultiPoint", "Punkte"),
|
||||
("none", "None", "ohne Geometrie"),
|
||||
)
|
||||
|
||||
|
||||
def _family(feature):
|
||||
"""Geometrie-Familie eines Features. Unbekannte Typen (z.B.
|
||||
GeometryCollection) landen bei 'none'."""
|
||||
if not feature.hasGeometry() or feature.geometry().isNull():
|
||||
return "none"
|
||||
gtype = feature.geometry().type()
|
||||
if gtype == _GEOM_POINT:
|
||||
return "point"
|
||||
if gtype == _GEOM_LINE:
|
||||
return "line"
|
||||
if gtype == _GEOM_POLYGON:
|
||||
return "polygon"
|
||||
return "none"
|
||||
|
||||
|
||||
def memory_layer_from_features(
|
||||
uri_type, name, fields, features, dataset_key, verfahren_nr, api_path, promote=True
|
||||
):
|
||||
"""Baut einen Memory-Layer aus fertigen QgsFeatures, setzt die
|
||||
Plugin-Custom-Properties und hängt ihn ins Projekt."""
|
||||
uri = uri_type if uri_type == "None" else "%s?crs=%s" % (uri_type, GEOJSON_CRS)
|
||||
layer = QgsVectorLayer(uri, name, "memory")
|
||||
if not layer.isValid():
|
||||
raise RuntimeError("Memory-Layer '%s' konnte nicht erzeugt werden." % name)
|
||||
provider = layer.dataProvider()
|
||||
provider.addAttributes(fields.toList())
|
||||
layer.updateFields()
|
||||
prepared = []
|
||||
for feature in features:
|
||||
geometry = feature.geometry()
|
||||
if promote and not geometry.isNull() and not geometry.isMultipart():
|
||||
geometry.convertToMultiType()
|
||||
feature.setGeometry(geometry)
|
||||
prepared.append(feature)
|
||||
provider.addFeatures(prepared)
|
||||
layer.updateExtents()
|
||||
layer.setCustomProperty(PROP_DATASET, dataset_key)
|
||||
layer.setCustomProperty(PROP_VERFAHREN, verfahren_nr)
|
||||
layer.setCustomProperty(PROP_PATH, api_path)
|
||||
return layer
|
||||
|
||||
|
||||
def feature_collection_to_layers(
|
||||
feature_collection, base_name, default_geometry, dataset_key, verfahren_nr, api_path
|
||||
):
|
||||
"""Erzeugt aus einer GeoJSON FeatureCollection je vorkommender
|
||||
Geometrie-Familie einen Memory-Layer und hängt sie ins Projekt.
|
||||
Liefert die Liste der erzeugten Layer."""
|
||||
text = json.dumps(feature_collection)
|
||||
fields = QgsJsonUtils.stringToFields(text)
|
||||
features = QgsJsonUtils.stringToFeatureList(text, fields)
|
||||
|
||||
groups = {}
|
||||
for feature in features:
|
||||
groups.setdefault(_family(feature), []).append(feature)
|
||||
|
||||
layers = []
|
||||
multiple = len(groups) > 1
|
||||
|
||||
def make_layer(uri_type, name, group_features, promote):
|
||||
layers.append(
|
||||
memory_layer_from_features(
|
||||
uri_type, name, fields, group_features,
|
||||
dataset_key, verfahren_nr, api_path, promote=promote,
|
||||
)
|
||||
)
|
||||
|
||||
if not groups:
|
||||
# Leerer Datensatz: ein leerer Layer im Default-Typ, damit der
|
||||
# Nutzer digitalisieren und hochladen kann.
|
||||
make_layer(default_geometry, base_name, [], promote=False)
|
||||
else:
|
||||
for family, uri_type, suffix in _FAMILIES:
|
||||
if family not in groups:
|
||||
continue
|
||||
name = "%s — %s" % (base_name, suffix) if multiple else base_name
|
||||
make_layer(uri_type, name, groups[family], promote=(family != "none"))
|
||||
|
||||
QgsProject.instance().addMapLayers(layers)
|
||||
return layers
|
||||
|
||||
|
||||
def plugin_layers(dataset_key, verfahren_nr):
|
||||
"""Alle Layer im Projekt, die zu diesem Datensatz und dieser VKZ
|
||||
gehören (z.B. die Punkte-/Linien-/Flächen-Teil-Layer von Plan 41)."""
|
||||
result = []
|
||||
for layer in QgsProject.instance().mapLayers().values():
|
||||
if (
|
||||
isinstance(layer, QgsVectorLayer)
|
||||
and layer.customProperty(PROP_DATASET) == dataset_key
|
||||
and str(layer.customProperty(PROP_VERFAHREN)) == str(verfahren_nr)
|
||||
):
|
||||
result.append(layer)
|
||||
return result
|
||||
|
||||
|
||||
def layers_to_feature_collection(layers):
|
||||
"""Vereint die Features mehrerer Layer zu EINER GeoJSON
|
||||
FeatureCollection (für den PUT, der den Serverbestand ersetzt).
|
||||
|
||||
Keine Transformation nach WGS84: Die API erwartet die Koordinaten im
|
||||
selben CRS, in dem sie sie liefert (EPSG:25833)."""
|
||||
merged = []
|
||||
for layer in layers:
|
||||
exporter = QgsJsonExporter(layer)
|
||||
exporter.setSourceCrs(layer.crs())
|
||||
exporter.setTransformGeometries(False)
|
||||
collection = json.loads(
|
||||
exporter.exportFeatures(list(layer.getFeatures()))
|
||||
)
|
||||
merged.extend(collection.get("features", []))
|
||||
return {"type": "FeatureCollection", "features": merged}
|
||||
|
||||
|
||||
def layer_api_path(layer):
|
||||
"""API-Pfad, unter dem dieser Layer geladen wurde — oder None,
|
||||
wenn der Layer nicht von diesem Plugin stammt."""
|
||||
if not isinstance(layer, QgsVectorLayer):
|
||||
return None
|
||||
return layer.customProperty(PROP_PATH) or None
|
||||
Reference in New Issue
Block a user