162 lines
7.0 KiB
Python
162 lines
7.0 KiB
Python
"""Ein Verfahren in QGIS rendern — echte Daten, Stil wie vom Server, Plugin-Code fuer die Grafiken.
|
|||
|
|
|
||
|
|
Sichtpruefung der Verkehrszeichen ohne Zugangsschluessel. Entstanden am 23.09.2026, als
|
||
|
|
unter den Schildern Fragezeichen standen und ein gedrehtes Schild aufrecht blieb — beides
|
||
|
|
sah man erst im echten Bild, nicht im Quelltext des Stils.
|
||
|
|
|
||
|
|
Aufruf (VKZ als zweites Argument, Vorgabe 22017; Bilder landen als <ziel>_*.png):
|
||
|
|
|
||
|
|
APP=/Applications/QGIS-final-4_2_0.app; LIB="$APP/Contents/Resources/python3.12"
|
||
|
|
QT_QPA_PLATFORM=offscreen PROJ_DATA="$APP/Contents/Resources/qgis/proj" \\
|
||
|
|
PYTHONPATH="$LIB:$LIB/lib-dynload:$LIB/site-packages" \\
|
||
|
|
"$APP/Contents/MacOS/python3.12" tools/headless_render_live.py /tmp/vln 22017
|
||
|
|
|
||
|
|
Braucht den Klon des Managers fuer den Stil-Erzeuger (Umgebungsvariable VLN_MANAGER_REPO,
|
||
|
|
Vorgabe ~/GIT/VLNManager) — derselbe Code, der auf dem Server laeuft.
|
||
|
|
|
||
|
|
Weg wie im Plugin, nur ohne Anmeldung:
|
||
|
|
* Daten: GET /v2/maps/widmung/22017 (oeffentlich) — dieselben Objekte wie die OGC-Sammlung.
|
||
|
|
* `zusatz` als TEXT, so wie der OAPIF-Provider es fuehrt (die Queryables melden "string").
|
||
|
|
* Stil: require.widmung_stil.qml() — derselbe Code, der auf S1 laeuft (Checkout = Repo).
|
||
|
|
* Grafiken: vln_karten.zeichen_katalog (sichere_svgs, pfad_registrieren, stil_lokalisieren).
|
||
|
|
"""
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import urllib.request
|
||
|
|
|
||
|
|
sys.path.insert(0, os.environ.get("VLN_MANAGER_REPO", os.path.expanduser("~/GIT/VLNManager")))
|
||
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
|
|
from qgis.core import QgsApplication
|
||
|
|
|
||
|
|
app = QgsApplication([], True)
|
||
|
|
QgsApplication.setPrefixPath("/Applications/QGIS-final-4_2_0.app/Contents/MacOS", True)
|
||
|
|
QgsApplication.setPluginPath("/Applications/QGIS-final-4_2_0.app/Contents/PlugIns/qgis")
|
||
|
|
app.initQgis()
|
||
|
|
|
||
|
|
from qgis.PyQt.QtCore import QMetaType, QSize
|
||
|
|
from qgis.PyQt.QtGui import QColor, QImage, QPainter
|
||
|
|
from qgis.PyQt.QtNetwork import QNetworkProxyFactory
|
||
|
|
from qgis.core import (QgsCoordinateReferenceSystem, QgsExpressionContext,
|
||
|
|
QgsExpressionContextUtils, QgsFeature, QgsField, QgsGeometry,
|
||
|
|
QgsMapRendererCustomPainterJob, QgsMapSettings, QgsPointXY,
|
||
|
|
QgsRectangle, QgsVectorLayer)
|
||
|
|
|
||
|
|
QNetworkProxyFactory.setUseSystemConfiguration(False)
|
||
|
|
|
||
|
|
from require import widmung_stil as W
|
||
|
|
from vln_karten import zeichen_katalog as zk
|
||
|
|
from vln_karten.api_client import KartenApiClient
|
||
|
|
|
||
|
|
API = "https://api.flurneuordnung-sachsen.de"
|
||
|
|
ZIEL = sys.argv[1]
|
||
|
|
VKZ = sys.argv[2] if len(sys.argv) > 2 else "22017"
|
||
|
|
|
||
|
|
fc = json.load(urllib.request.urlopen(API + "/v2/maps/widmung/" + VKZ, timeout=30))
|
||
|
|
zeichen = [f for f in fc["features"] if (f.get("properties") or {}).get("typ") == "verkehrszeichen"]
|
||
|
|
linien = [f for f in fc["features"] if (f.get("properties") or {}).get("typ") == "widmung"]
|
||
|
|
print("Live-Daten: %d Zeichen, %d Widmungslinien" % (len(zeichen), len(linien)))
|
||
|
|
|
||
|
|
|
||
|
|
def punkt_layer():
|
||
|
|
layer = QgsVectorLayer("Point?crs=EPSG:25833", "Zeichen " + VKZ, "memory")
|
||
|
|
dp = layer.dataProvider()
|
||
|
|
dp.addAttributes([QgsField("vz", QMetaType.Type.QString),
|
||
|
|
QgsField("zusatz", QMetaType.Type.QString),
|
||
|
|
QgsField("drehung", QMetaType.Type.Double),
|
||
|
|
QgsField("groesse", QMetaType.Type.Int),
|
||
|
|
QgsField("vz_status", QMetaType.Type.QString)])
|
||
|
|
layer.updateFields()
|
||
|
|
neu = []
|
||
|
|
for f in zeichen:
|
||
|
|
p = f["properties"]
|
||
|
|
x, y = f["geometry"]["coordinates"][:2]
|
||
|
|
q = QgsFeature(layer.fields())
|
||
|
|
q.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))
|
||
|
|
# So fuehrt der OAPIF-Provider das Feld: als Text.
|
||
|
|
q.setAttributes([str(p.get("vz") or ""), json.dumps(p.get("zusatz") or []),
|
||
|
|
float(p.get("drehung") or 0), int(p.get("groesse") or 2),
|
||
|
|
str(p.get("vz_status") or "")])
|
||
|
|
neu.append(q)
|
||
|
|
print(" Zeichen %-4s zusatz=%-14s drehung=%s" % (p.get("vz"), json.dumps(p.get("zusatz")),
|
||
|
|
p.get("drehung")))
|
||
|
|
dp.addFeatures(neu)
|
||
|
|
layer.updateExtents()
|
||
|
|
return layer
|
||
|
|
|
||
|
|
|
||
|
|
def linien_layer():
|
||
|
|
layer = QgsVectorLayer("LineString?crs=EPSG:25833", "Widmung " + VKZ, "memory")
|
||
|
|
dp = layer.dataProvider()
|
||
|
|
dp.addAttributes([QgsField("strassenklasse", QMetaType.Type.QString),
|
||
|
|
QgsField("widmung_status", QMetaType.Type.QString),
|
||
|
|
QgsField("neue_klasse", QMetaType.Type.QString)])
|
||
|
|
layer.updateFields()
|
||
|
|
neu = []
|
||
|
|
for f in linien:
|
||
|
|
p = f["properties"]
|
||
|
|
q = QgsFeature(layer.fields())
|
||
|
|
q.setGeometry(QgsGeometry.fromPolylineXY(
|
||
|
|
[QgsPointXY(x, y) for x, y, *_ in f["geometry"]["coordinates"]]))
|
||
|
|
q.setAttributes([str(p.get("strassenklasse") or ""), str(p.get("widmung_status") or ""),
|
||
|
|
str(p.get("neue_klasse") or "")])
|
||
|
|
neu.append(q)
|
||
|
|
dp.addFeatures(neu)
|
||
|
|
layer.updateExtents()
|
||
|
|
return layer
|
||
|
|
|
||
|
|
|
||
|
|
zeichen_layer = punkt_layer()
|
||
|
|
widmung_layer = linien_layer()
|
||
|
|
|
||
|
|
# Plugin-Weg: Grafiken holen, Suchpfad eintragen, Stil auf die lokale Ablage umschreiben.
|
||
|
|
client = KartenApiClient()
|
||
|
|
codes = zk.codes_im_layer(zeichen_layer)
|
||
|
|
zk.pfad_registrieren()
|
||
|
|
geholt, fehlend = zk.sichere_svgs(client, codes)
|
||
|
|
print("Codes im Layer: %s | geholt %d | fehlend %s" % (sorted(codes), geholt, fehlend))
|
||
|
|
|
||
|
|
for layer, familie in ((zeichen_layer, "punkt"), (widmung_layer, "linie")):
|
||
|
|
qml = W.qml(familie, API + "/v2/zeichen/svg/", layer.name())
|
||
|
|
qml, ersetzt = zk.stil_lokalisieren(qml, API)
|
||
|
|
pfad = "/tmp/vln_live_%s.qml" % familie
|
||
|
|
with open(pfad, "w", encoding="utf-8") as fh:
|
||
|
|
fh.write(qml)
|
||
|
|
meldung, ok = layer.loadNamedStyle(pfad)
|
||
|
|
print("Stil %-6s geladen=%s lokalisiert=%s" % (familie, ok, ersetzt))
|
||
|
|
|
||
|
|
|
||
|
|
def rendern(massstab, breite, hoehe, datei, mitte=None):
|
||
|
|
mitte = mitte or zeichen_layer.extent().center()
|
||
|
|
m_je_px = massstab / 3779.5276
|
||
|
|
b, h = breite * m_je_px / 2, hoehe * m_je_px / 2
|
||
|
|
e = QgsMapSettings()
|
||
|
|
e.setLayers([zeichen_layer, widmung_layer])
|
||
|
|
e.setBackgroundColor(QColor(255, 255, 255))
|
||
|
|
e.setOutputSize(QSize(breite, hoehe))
|
||
|
|
e.setOutputDpi(96)
|
||
|
|
e.setDestinationCrs(QgsCoordinateReferenceSystem("EPSG:25833"))
|
||
|
|
e.setExtent(QgsRectangle(mitte.x() - b, mitte.y() - h, mitte.x() + b, mitte.y() + h))
|
||
|
|
ctx = QgsExpressionContext()
|
||
|
|
ctx.appendScope(QgsExpressionContextUtils.globalScope())
|
||
|
|
ctx.appendScope(QgsExpressionContextUtils.mapSettingsScope(e))
|
||
|
|
e.setExpressionContext(ctx)
|
||
|
|
bild = QImage(e.outputSize(), QImage.Format.Format_ARGB32)
|
||
|
|
bild.fill(QColor(255, 255, 255))
|
||
|
|
maler = QPainter(bild)
|
||
|
|
auftrag = QgsMapRendererCustomPainterJob(e, maler)
|
||
|
|
auftrag.start()
|
||
|
|
auftrag.waitForFinished()
|
||
|
|
maler.end()
|
||
|
|
bild.save(datei)
|
||
|
|
print(" 1:%-6d -> %s" % (round(e.scale()), datei))
|
||
|
|
|
||
|
|
|
||
|
|
print("Bilder:")
|
||
|
|
rendern(2000, 520, 700, ZIEL + "_beide.png")
|
||
|
|
for f in zeichen_layer.getFeatures():
|
||
|
|
rendern(500, 360, 360, ZIEL + "_%s.png" % f["vz"], f.geometry().asPoint())
|
||
|
|
rendern(5000, 500, 300, ZIEL + "_marke.png")
|
||
|
|
rendern(20000, 500, 300, ZIEL + "_punkt.png")
|
||
|
|
app.exitQgis()
|