diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 0000000..ab36ba8 --- /dev/null +++ b/.github/workflows/backport.yml @@ -0,0 +1,18 @@ +name: Backport +on: + pull_request_target: + types: + - closed + - labeled + +jobs: + backport: + runs-on: ubuntu-latest + name: Backport + steps: + - name: Backport Bot + id: backport + if: github.event.pull_request.merged && ( ( github.event.action == 'closed' && contains( join( github.event.pull_request.labels.*.name ), 'backport') ) || contains( github.event.label.name, 'backport' ) ) + uses: m-kuhn/backport@v1.2.7 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..404aeac --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,124 @@ +name: Tests + +on: + push: + branches: + - master + - ce + - lite + + pull_request: + branches: + - master + - ce + - lite + + workflow_dispatch: + +jobs: + commit-sha: + name: Get Commit Hash + runs-on: ubuntu-latest + + outputs: + commit_sha: ${{steps.get_hash.outputs.COMMIT_SHA}} + + steps: + - name: Checkout Project + uses: actions/checkout@v3 + + - name: commit sha + id: get_hash + run: echo "::set-output name=COMMIT_SHA::$(git rev-parse --short HEAD)" + + test: + name: Test on ${{matrix.container }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + container: ['qgis/qgis:release-3_22', 'qgis/qgis:release-3_24', 'qgis/qgis:release-3_26', 'qgis/qgis:release-3_28'] + + container: ${{ matrix.container }} + + steps: + + - name: Checkout Project + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + pip install pytest pytest-cov pytest-qt pytest-mock pytest-asyncio nest-asyncio + pip install -r requirements.txt + + - name: Unit Tests + env: + DISPLAY: :99 + run: | + xvfb-run qgis_testrunner.sh tests.run_tests.run_all + + alembic-revision: + name: Generate SQL Scripts + needs: [test, commit-sha] + runs-on: ubuntu-latest + + container: 'qgis/qgis:release-3_22' + + steps: + - name: Checkout Project + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + pip install alembic + pip install -r requirements.txt + + - name: Parse Alembic History + id: alembic_history + run: | + prev_revision=`alembic history | head -n1 | awk '{print $1}'` + revision_tag=`alembic history | head -n1 | awk '{print $NF}'` + prev_tag=`alembic history | head -n2 | tail -n1 | awk '{print $NF}'` + + echo "::set-output name=prev_revision::$prev_revision" + echo "::set-output name=revision_tag::$revision_tag" + echo "::set-output name=prev_tag::$prev_tag" + + - name: Generate Revisions + run: | + alembic upgrade head --sql > create_${{steps.alembic_history.outputs.revision_tag}}.sql + alembic downgrade head:${{steps.alembic_history.outputs.prev_revision}} --sql > downgrade_to_${{steps.alembic_history.outputs.prev_tag}}.sql + alembic upgrade ${{steps.alembic_history.outputs.prev_revision}}:head --sql > upgrade_from_${{steps.alembic_history.outputs.prev_tag}}.sql + + - name: Upload SQL Scripts + uses: actions/upload-artifact@v2 + with: + name: SAGis.XPlanung.sql-scripts@${{ needs.commit-sha.outputs.commit_sha }} + path: '*.sql' + + publish: + name: Upload artifact + needs: [test, commit-sha] + runs-on: ubuntu-latest + + steps: + - name: Checkout Project + uses: actions/checkout@v3 + + - name: copy source files and scripts + run: | + mkdir dist + cp -r src/SAGisXPlanung dist/XPlanung/ + + - uses: docker://pandoc/latex:2.9 + with: + args: --from markdown -o dist/README.pdf README.md + + - name: QGIS plugin distribution + uses: actions/upload-artifact@v2 + with: + name: SAGis.XPlanung.source@${{ needs.commit-sha.outputs.commit_sha }} + path: ./dist diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..02a72f3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,94 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + build: + runs-on: windows-latest + outputs: + upload_url: ${{ steps.create-release.outputs.upload_url }} + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Get Version Number + id: get_version_number + run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} + shell: bash + + - name: Pack Assets + id: pack_assets + run: | + Compress-Archive .\SAGisXPlanung SAGis.XPlanung_${{ steps.get_version_number.outputs.VERSION }}.zip + working-directory: src + + - name: Create Installer + id: create_installer + run: | + Copy-Item -Path "..\src\SAGisXPlanung" -Destination . -Recurse + + python3 -m pip download sqlalchemy==1.4.46 GeoAlchemy2 qasync shapely==2.0.0 lxml asyncpg --dest dependencies + + iscc.exe /DSAGIS_VERSION=${{ steps.get_version_number.outputs.VERSION }} "setup.iss" + iscc.exe /DSAGIS_VERSION=${{ steps.get_version_number.outputs.VERSION }} /DWITH_CIVIL_IMPORT "setup.iss" + working-directory: installer + + - name: Create Release + id: create-release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ github.ref }} + name: Release ${{ steps.get_version_number.outputs.VERSION }} + body: "" + prerelease: false + files: | + src/SAGis.XPlanung_${{ steps.get_version_number.outputs.VERSION }}.zip + installer/Output/*.exe + + alembic-revision: + name: Generate SQL Scripts + needs: [build] + runs-on: ubuntu-latest + + container: 'qgis/qgis:release-3_28' + + steps: + - name: Checkout Project + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + pip install alembic + pip install -r requirements.txt + - name: Parse Alembic History + id: alembic_history + run: | + prev_revision=`alembic history | head -n1 | awk '{print $1}'` + revision_tag=`alembic history | head -n1 | awk '{print $NF}'` + prev_tag=`alembic history | head -n2 | tail -n1 | awk '{print $NF}'` + echo "::set-output name=prev_revision::$prev_revision" + echo "::set-output name=revision_tag::$revision_tag" + echo "::set-output name=prev_tag::$prev_tag" + - name: Generate Revisions + run: | + alembic upgrade head --sql > create_${{steps.alembic_history.outputs.revision_tag}}.sql + alembic downgrade head:${{steps.alembic_history.outputs.prev_revision}} --sql > downgrade_to_${{steps.alembic_history.outputs.prev_tag}}.sql + alembic upgrade ${{steps.alembic_history.outputs.prev_revision}}:head --sql > upgrade_from_${{steps.alembic_history.outputs.prev_tag}}.sql + - name: Package Files + run: | + apt-get install zip + zip -R sql_scripts.zip *.sql + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.build.outputs.upload_url }} + asset_path: ./sql_scripts.zip + asset_name: sql_scripts.zip + asset_content_type: application/zip diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f089ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,143 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# pycharm +.idea/ + +installer/*/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..48c63e0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,532 @@ +## Changelog + +### [2.2.0] - 29.08.2023 + +#### Neue Funktionen + +- Funktion zum Zurücksetzen der Geometrievaldierung +- Unterstützung von Codelist-Attributen: + - Möglichkeit zum Herunterladen von Werten einiger XPlanung-Codelisten aus der GDI-DE Registry +- Neue Objektklassen: + - `XP_Hoehenangabe`, `BP_NutzungsartenGrenze`, `BP_BereichOhneEinAusfahrtTypen`, `BP_EinfahrtPunkt` + +#### Veränderungen + +- Ausblenden von Attributen betrifft nun auch die Dialoge zum Bearbeiten von Atrributen (vormals nur bei Erfassung neuer Planinhalte) (a69cde81) + +#### Fehlerbehebungen + +- Fehler behoben, bei dem das Speichern von Planinhalten keine Änderung im Objektbaum verursachte (580723e4) +- fehlerhafter GML-Export des Attributs `rechtscharakter` in der Version 5.3 korrigiert (6a0e1e7b) +- Bearbeiten von Plänen mit `BP_VeraenderungssperreDaten` möglich (24dab1e5) + +### [2.1.1] - 16.08.2023 + +#### Fehlerbehebungen + +- Speichern von Objekten mit Angabe des Attributs `rechtscharakter` korrigiert + +### [2.1.0] - 01.08.2023 + +#### Neue Funktionen + +- Funktion zum "Rückgängig machen" von Attribut-Änderungen und Löschen einzelner Planinhalte +- Überprüfung und Installation aller Python-Abhängigkeiten bei Start der Anwendung +- Funktion zum Anlegen einer XPlanung-Datenbank aus der Anwendung + +- [internal] Backport-System zur parallelen Entwicklung verschiedener Versionsstände + +#### Veränderungen + +- Allgemeine Überarbeitung des Einstellungsdialog: bessere Strukturierung +- Verschieben von Präsentationsobjekte kann abgebrochen werden (3474b5d7) +- neue Tooltips für XPlan-Attribute der XPlanung-Version 6.0 (3dd603af) +- [refactor] Finden von zusammengehörigen Planinhalten (83e7c43f) +- [refactor] Aktualisieren des Objekt-Exploreres nach Erstellen des Flächenschluss lädt auschließlich veränderte Objekte neu (b1ebff3d) + +#### Fehlerbehebungen + +- fehlerhafter Aufruf des Civil3D-Imports durch Menüeintrag korrigiert (9213a4d0) +- Objektklassen, die nicht zur gewählten XPlanung-Version passen, werden nicht mehr exportiert (cc103025) +- fehlende Umsetzung des CR-039 im BPlan-Schema für XPlanung 6.0 (d352a4cb, 853bd728) +- fehlende Anzeige von Tooltips für XPlan-Attribute (82c53d7c) +- GML Import bricht nicht mehr bei leeren Tags ab, sondern ignoriert diese (da0884d1) +- GML Import von Nutzungsschablonen mit 2 oder 4 Zeilen (bd393433) +- fehlerhafte SQL-Revision in 2.0.0: Commit nötig nach Enum-Veränderungen (51c48377) + +### [2.0.0] - 29.06.2023 + +#### Neue Funktionen + +- Abbildung des Objektmodells in der XPlanung-Version 6 +- Import/Export von XPlanGML 6.0 + + +- Mehrfachauswahl zum Löschen von Objekten im Objektbaum (8a3d2112) +- Fortschrittsindikator beim Import von XPlanGML-Dateien (65cf9b2e) + +#### Veränderungen + +- Geometrievalidierung funktioniert mit Curved-Geometries (d10e0869) +- Performance-Verbesserungen der Geometrievalidierung (d10e0869) +- Kleine Performace-Verbesserungen beim Import von XPlanGML-Dateien (3eff74ba) + +#### Fehlerbehebungen + +- Datenbankinhalte werden beim ersten Start der Anwendung korrekt angezeigt (99b93aec) +- Import funktioniert auch, wenn Attribut `srsName` nicht auf dem äußerten Elememt vorliegt (8438b4e6) +- Multiplizität des Attributs `zweckbestimmung` der Klasse `FP_Gemeinbedarf` korrigiert (18417f4b) +- Korrektur der Werte `AnlageBundLand` und `BetriebOeffentlZweckbestimmung` der Enumklasse `XP_ZweckbestimmungGemeinbedarf` (0dff22e9) + +### [1.10.2] - 12.05.2023 + +#### Veränderungen + +- Datenmodell unterstützt das Speichern und Anzeigen von Objekten mit unterschiedlicher Geometriedimension (c6c7a721) +- Auswahllisten zur Bauweise und Bebaungsart sind nicht mehr Pflichtattribute (b77a2cd0) +- Zwischenspeichern des zuletzt genutzten Ordners beim Exportieren (76068599) +- Verbesserung der Fehlermeldungen beim Prüfen der Konformitätsbedingungen von Baugebieten (51403247) +- Umbennung des Basisverzeichnis und Python-Moduls in `SAGisXPlanung` (44b0cc0e) + + +- [Installer] Installationsprogramm lädt alle Abhängigkeiten aus lokalen Paketen (eff32a66) +- [Installer] Installationsprogramm prüft, ob Pythons Paketmanager installiert ist (05e6a10c) + +#### Fehlerbehebungen + +- Import von XPlanGML mit Objekten vom Typ `XP_SPEMassnahmenDaten` produziert keine Fehlermeldung mehr (287cf661) +- Fehler behoben, durch den bestimmte Attributwerte nicht gelöscht werden konnten (8da7221a) +- Crash bei Anzeige von Objekten des Typs `SO_Denkmalschutzrecht` unter QGIS >= 3.24 (7a9d3acf) +- fehlendes Kontextmenü im Objektbaum für Klasse `BP_SchutzPflegeEntwicklung` (c50961c9) +- fehlende Attributformulare für Datenobjekte (0b461799) +- fehlende Möglichkeit zur Dialog-Konfiguration für Klassen `XP_Bereich` und `XP_Objekt` (510011b2) + +### [1.10.1] - 13.04.2023 + +#### Veränderungen + +- Geometrievalidierung funktioniert mit Curved-Geometries (d10e0869) +- Performance-Verbesserungen der Geometrievalidierung (d10e0869) + +#### Fehlerbehebungen + +- fehlendes Attribut `flaechenschluss` bei Objektklassen, die von *P_Flaechenschlussobjekt abgeleitet sind (c353674f) +- Fehler bei der Anzeige von Geometriefehlern behoben, wenn es sich um eine Multi-Geometrie handelt (2ef3fa02) +- Auswahl der korrekten Seite im Einstellungs-Dialog (bdf6d65f) + +### [1.10.0] - 28.03.2023 + +#### Neue Funktionen + +- Unterstützung für Geometrien nach SQL/MM Spatial Part 3 (Curved Geometries) + - benötigt mindestens GDAL 3.6.3 +- Einstellungen zur Konfiguration der Sachdatendialoge: + - Ausblenden von irrelevanten XPlanung-Attributen +- Überarbeitung des Dialogs zum Konfigurieren der Planinhalte: + - Konfigurieren mehrerer gleicher Planinhalte in einem Schritt möglich + - Auswahl von Geometrien über QGIS-Abfrageausdrücke +- neuer Dialog zum nachträglichen Bearbeiten der Sachdaten eines Plans + - Zuweisung eines neuen Geltungsbereichs aus einem Layer möglich + +#### Veränderungen + +- Nutzung des GML-Drivers von OGR/GDAL, anstatt der GML-API von QGIS +- Nutzung des Geometriemodells von GDAL anstatt shapely zur flexiblen Unterstützung verschiedenster Geomtrietypen +- Geometrien von neu angelegten Planinhalten werden nicht mehr segmentiert (f01ab139) +- Neue Menüeinträge in der SAGis-Menüleiste zum schnelleren Abruf der Verarbeitungswerkzeuge (3fd2a47f) +- Installationsprogramm erzwingt Installation von sqlalchemy in der Version 1.4 +- Import-Button bleibt deaktviert, solange keine Datei angegeben ist +- bessere Bezeichnung des Tabellenkopfes beim Bearbeiten von Attributen + +#### Fehlerbehebungen + +- fehlerhaftes Layout in den Sachdaten-dialogen behoben (98a353df) +- Fehler behoben, bei dem ein Planwerk aus Civil3D nicht importiert werden konnte, wenn sich noch kein Plan in der Datenbank befindet (c10916b0) +- keine Fehlermeldung mehr, wenn SAGis XPlanung vor den internen QGIS-Plugins geladen wurde (31f8dae3) +- Fehler beim Import von XPlanGML-Dokumenten mit fehlenden `xlink`-Verweisen behoben (31195ced) +- Installationsprogramm öffnet nun auch QGIS-LTR nach der Installation von SAGis XPlanung (628c63d6) + +### [1.9.0] - 06.01.2023 + +#### Neue Funktionen + +- Neues Verarbeitungswerkzeug zum Importieren von BPlan-Daten aus Civil3D + +#### Veränderungen + +- API für gemeinsame Menüs und Toolbars aller SAGis-Lösungen im QGIS (a288dd47) +- Überarbeitung der Funktionen zum Annotatieren von Planwerken (v2): + - neuer Dialog zum Anlegen und Konfigurieren von Annotationen (0c2f5789) + - Präsentationsobjekte werden nun einem bestimmten Planinhalt zugeordnet (nicht mehr dem gesamten Plan) + - neue Präsentationsobjekte erscheinen sofort auf der Karte + - Nutzung der korrekten Objektklasse `XP_PTO` statt `XP_TPO` für punktförmige Textannotationen (a916c05d) + - XPlanGML-Import/Export von Präsentationsobjekten + +- Kartenanzeige von Objekten der Klasse `SO_Denkmalschutzrecht` verbessert (e12f75a8) +- Weitere Tooltips beim Bearbeiten von Attributen (cce65474) +- Vererbungshierarchie für externe Referenzen angepasst, um das XPlanung-Schema besser abzubilden (a9debc85) + +#### Fehlerbehebungen + +- Attribute mit dem Datentyp `Angle` erlauben gebrochene Zahlen wie in GML-Spezifikation vorgegeben (e289a41c) +- Falsche Default-Werte bei den Zeilen/Spalten von Nutzungsschablonen behoben (f6c7dfeb) +- Tooltips mit langem Textinhalt überdecken nicht mehr die gesamte Bildschirmbreite (24c2b4dd) +- fehlende Hervorhebung von Pflichtattributen beim Erstellen von Plänen hinzugefügt (24c2b4dd) + +### [1.8.0] - 06.01.2023 + +#### Neue Funktionen + +- Überprüfung der Konformitätsbedingungen für Flächennutzungspläne +- neue Objektklassen für nachrichtliche Übernahmen im FNP: + - `SO_Denkmalschutzrecht`, `SO_Schienenverkehrsrecht`, `SO_SchutzgebietWasserrecht` + +#### Veränderungen + +- Überarbeitung der Funktionen zum Annotatieren von Planwerken: + - neues Widget zum Ändern des SVG-Symbols einer Annotation (#35) + - verbesserte Sortierung der Symbolbibliothek (7824d697, 1a672240) + - neue Funktion zum Verschieben von Annotationen auf der Karte (#34) + - neue Funktion zum Abfragen von Annotationen auf der Karte + +- Überarbeitung der Geometrievalidierung (#42): + - vollständig asynchrone Ausführung der Validierung (QGIS bleibt für andere Funktionen bedienbar) + - Starke Verbesserung der Geschwindigkeit der Validierung durch + - Nutzung von R-Tree Spatial Index zur Vermeidung von leistungsintensiven Geometrievergleichen + - Weniger und besser optimierte SQL-Abfragen + - -> 80% schneller auf kleinen Planwerken (ca. 100 Objekte), bis zu 93% schneller auf großen Planwerken (ca. 1000 Objekte) + +- Tooltips zur Erklärung aller XPlanung-Attribute in sämtlichen Formularen (34b536f4) +- neues Framework für Enum-Werte, die erlauben, keine Auswahl zu treffen (b992a597) + +#### Fehlerbehebungen + +- Falsche Kardinalität des Attributs `sondernutzung` behoben (94d8da63) + +### [1.7.0] - 28.10.2022 + +#### Neue Funktionen + +- Neue Objektklassen für Flächennutzungspläne +- Abfrage-Tool ermöglicht das Identifizieren von Präsentationsobjekten auf dem Kartencanvas + +#### Veränderungen + +- Performance: + - Dateiinhalte werden nur bei Bedarf aus der Datenbank geladen (2ea67614) + - Datenbankabfragen laden nur noch benötigte Spalten (c0c79ada) + - Dateireferenzen größer als 100MB werden nicht mehr erlaubt (63562b5a) + +- Usability: + - bessere Beschreibungen der Attribute (17dc8efb) + - Objektbearbeitung im Objektbaum kann mit Doppelclick aufgerufen werden (c4da5383) + - Datenobjekte werden asynchron gespeichert, UI bleibt bedienbar (b947081d) + - Anzeige von unnötigen Attributen entfernt (042f58e5) + - Aussehen des Fensters beim Zeigen von Planwerken mit langem Namen verbessert (f2a3ae13, 37352e14) + +- Kartensymbole vergrößert (3ea4ef57) +- Unterstützung für QGIS-Versionen unter 3.22 aufgehoben (63f5b598) +- größere Auswahl an SVG-Symbolen zum Annotieren der Planwerke (e83f7827) + +#### Fehlerbehebungen + +- Fehler beim Anzeigen von Fenstern mit horizantalem Scroll behoben (a272236a) +- Änderungen von Plannamen werden überall korrekt angezeigt (a768c63a) +- Rasterlayer werden beim Neuladen des Projekts auch neu gerendert (1c028492) +- Export von Enum-Listen ins XPlanGML korrigiert (91b28c2e) +- fehlende Layer-Felder zur korrekten regelbasierten Anzeige bestimmter Layer hinzugefügt (c52c3809) +- Tippfehler im Attributnamen `sondernutzung` behoben (191c57b3) +- Tippfehler in Werten des Enums `FP_ZweckbestimmungStrassenverkehr` behoben (5bd30cbb) +- Fehlende Attribute von Hinzugefügten Datenobjekten werden sofort neugeladen (482baeb4) +- Fehler beim Import von XPlanGML-Dokumenten mit Top-Level-Namespace behoben (3068a8e2) +- fehlende Dokumente beim Export ins ZIP-Archiv (389f9d9c) +- Ladeanimation stoppt bei Fehlern im Prozess des Hinzufügens von Datenobjekten (58421ebe) +- XPlanung MeasureTypes (Winkel, Volumen, etc.) mit korrektem Datentyp in der Datenbank gespeichert (8542387d) + + +- Komplieren von UI-Files mit custom top level widgets ermöglicht (8594099d) +- Fehler beim Generieren von Datenbank Revisionen mit Geometrie-Spalten behoben (2709a36f) + +### [1.6.0] - 29.08.2022 + +#### Neue Funktionen + +- Darstellung von Baunutzungsschablonen (`XP_Nutzungsschablone`): + - Möglichkeit zum Aktivieren/Deaktivieren der Nutzungschablone für Baugebietsflächen + - Bearbeiten von Form, Größe und Anordnung der tabellarischen Darstellung + - Identifikation und beliebige Positionierung auf der Karte über das Abfrage-Werkzeug + - XPlanGML-Export angelegter Baunutzungsschablonen +- Neue Option zum Bearbeiten/Zuweisen der Gemeinde eines Planwerks aus dem Detail-Fenster +- Neues QGIS-Verarbeitungswerkzeug zum Export aller Bauleitpläne (Batch-Export) + +#### Veränderungen + +- Abfrage-Werkzeug lässt sich mit Hotkey öffnen (d46c2912) +- Auswahl des Planwerks bleibt über verschiedene Fenster synchronisiert (85fda4cd) +- Geltungsbereich-Validierung beim Zuweisen von Geometrien zu einzelnen Planinhalten (c1471e17) +- Interne Verarbeitung von `XP_Gemeinde`-Objekten überarbeitet, Nutzung des Attributs `ortsteilName` wird dadurch + ermöglicht (06c086f9, 73e8a043) +- Interface für die Übernahme von XPlanung-Daten aus Civil (79b57bed) +- Überprüfung des korrekten Datenbank-Schemas bei Verbindungsaufbau (15767bef) + +#### Fehlerbehebungen + +- Fehler beim Export von Planwerken mit Sonderzeichen behoben (a3ea1f22) +- XPlanung-Kennzeichen wird auch beim Verschieben der Layer in Gruppen erhalten (0ffb3e86) +- Fehler behoben, durch den Knoten nicht sofort im Objektbaum erschienen (08a2b6fc) +- Fehler beim Import von XPlanGML, die das `wirdDargestelltDurch`-Attribut enthalten, behoben (b642c86c) +- Unglückliche Bezeichnung der Bereichs-Layer bei Nutzung mehrerer Bereiche behoben (80279eda) +- Auswahl des Features beim Zuweisen von Geometrien (`QgsFeaturePickerWidget`) führt nicht mehr dazu, + dass Eingabeelemente größer als das Fenster anwachsen (14218bb5) +- leere Attributgruppen erscheinen nicht mehr im Dialog zum Erstellen von Planinhalten (1129b97e) +- Auswahlliste der Planwerke wechselt nicht mehr zum ersten Objekt beim Verlieren des Fokus (0ddf379a) +- Dialog zum Bearbeiten der Stammdaten kann nicht mehr ohne Datenbanl-Verbindung geööfnet werden (f2aa326) +- Fehlender Titel des Fenster zum Bearbeiten von XPlanung-Objekten hinzugefügt (1acce23) + + +- Falsches Wurzelverzeichnis beim Generieren eines Release (de1cb31) +- xsd-Files werden zur Schema-Validierung nicht mehr online-abgerufen, damit können Tests auch ohne Internet + durchgeführt werden (1baf763b) + + +### [1.5.2] - 29.06.2022 + +#### Neue Funktionen + +- Bearbeiten von Datenobjekten wie `XP_Gemeinde` und `XP_Plangeber`: + - neuer Dialog zum Erstellen, Bearbeiten und Löschen von Datenobjekten + - Kontextmenu zum Bearbeiten und Löschen beim Erstellen eines Planwerks +- Neue 'SAGis'-Toolbar, die alle SAGis-Werkzeuge gruppiert + +#### Veränderungen + +- Installationsprogramm führt Prozesse im Hintergrund aus (b8ab4f0f) +- Fehlende Konformitätsbedingung 3.2.5.1 hinzugefügt (42729e62) + +#### Fehlerbehebungen + +- Fehler behoben, durch den das Abfragewerkzeug nicht das Attributfenster öffnen konnte (8ed3c71e) + +### [1.5.1] - 17.06.2022 + +#### Neue Funktionen + +- Objektbaum unterstützt Sortieren (nach Objekthierarchie, Alphabet und Kategorie) und Filtern + +#### Veränderungen + +- mehr Tooltips (d5c0f2a5) +- bessere Eingabefelder für `Boolean` Werte (a2b2d593) +- bessere Eingabefelder für Datumslisten (c3e71c18) +- bessere Eingabefelder für Datumswerte, die auch leere Eingaben erlauben (59767fba) +- verbessertes Aussehen der Formulare durch gleiche Ausrichtung und Breite aller Eingabeelemente (2df91316) +- neu hinzugefügte Objekte werden im Objektbaum markiert (7f578267) +- weniger auffälliger Farb-Effekt beim Auswählen von Objekten im Objektbaum + Hervorheben der Objekte beim Hovern (6da89a98) + +#### Fehlerbehebungen + +- kleine Fehlerbehebungen der in 1.5.0 überarbeiteten Eingabeformulare: + - Fehler beim Einlesen von Datumswerten und `MeasureTypes` behoben (fb17429b) + - Validierung wird nun auch beim Bearbeiten von Attributen korrekt angewendet (324f4fb3) + - neuer visueller Stil wird auch auf alte Elemente korrekt angewendet (db6a9bca) +- Default-Werte erscheinen beim Bearbeiten einzelner Attribute (10737af8) +- Fehler behoben, bei dem leere Eingabeformulare angezeigt wurden (415f6f55) +- Fehler durch nicht geschlossenen Dateizugriff behoben (5da6cba0) +- Funktionalität des _Verwerfen_-Buttons beim Bearbeiten von Attributen wiederhergestellt (f5409d45) +- Fehler bei der `RegEx`-Validierung behoben (24cabdec) +- Fehler behoben, durch den Beschriftungen breiter als das Fenster erschienen (24cabdec) +- Fehler behoben, durch den das Abfragewerkzeug nicht das Attributfenster öffnen konnte (08b94152) +- Löschen von mehreren aufeinanderfolgenden Objekten im Objektbaum korrigiert (460491db) + +### [1.5.0] - 11.05.2022 + +#### Neue Funktionen + +- `Darstellungsoptionen`: Neues Fenster zum Anpassen von Größe und Drehwinkel von Präsentationsobjekten +- Neue Objektarten: + - `BP_SchutzPflegeEntwicklungsFlaeche`, `BP_Wegerecht`, `RP_Plan`, `LP_Plan` + +#### Veränderungen + +- Verbesserung der Nutzungsfreundlichkeit aller Eingabeformulare: + - Beschreibung und Link zum Navigieren zu abhängigen Objekten + - Fehlerhafte Eingaben in Formularen werden alle gleichermaßen visuell hervorgehoben + - Eingabeformulare fokussieren automatisch fehlerhafte Eingaben +- Planinhalte mit fehlenden oder fehlerhaften Geometrien werden nicht mehr importiert (33fe00f8) + +#### Fehlerbehebungen + +- Planwerk-Details schließt nun wenn kein Planwerk mehr vorhanden ist (358b5883) +- Präsentationsobjekte werden nach Erfassung dem Objektbaum hinzugefügt (6944054b) +- Einige Beziehung zwischen Objektklassen von XPlanung korrigiert (Multiplizitäten, korrekte Vererbungshierarchie, etc.) +- fehlerhafter Zustand der Schaltfläche zum Speichern der Einstellungen behoben (a0ef802c) +- Schaltflächen werden korrekt deaktiviert, wenn kein Plan vorhanden ist (07833818) +- fehlerhafte Platzierung von Symbolen außerhalb der zugehörigen Planflächen behoben (c0e05de5) +- Fehler behoben, durch den auf manchen Rechnern keine Log-Datei geschrieben wurde (77cff8b4) +- Das Hervorheben von Präsentationsobjekten auf der Karte funktioniert nun richtig (0153daaf) + +#### Verschiedenes + +- Ab Version 1.5.0 existiert nun ein Installationsprogramm, welches das QGIS Plugin und alle Python-Abhängigkeiten installiert. + +### [1.4.3] - 11.03.2022 + +#### Neue Funktionen + +- Import von ZIP-Archiven mit Plandokumenten und externen Referenzen +- Import von Punktgeometrien in Plandokumenten +- [build] SAGis XPlanung kann mit dem Cython-Compiler in C-Programmcode übersetzt werden + +#### Veränderungen + +- Das Wechseln zwischen Planwerken blockiert nicht mehr die Benutzeroberfläche (150f3e6) +- Performance-Verbesserungen bei der Geometrievalidierung (5a8f4d1) +- Dockwidget-Fenster lassen sich nur Rechts und Links andocken (cce598a) +- Nutzung des `pyqtSlot`-Dekorator für alle Slot-Funktion bringt leichte Performanceverbesserung (95aaa76) +- Animation beim Ladevorgang des Detail-Fensters (44a2175) +- Neues Icon für das Plugin (9d3f876) +- Geometrien lassen sich nicht mehr im Attribut-Fenster bearbeiten (5c50718) + +#### Fehlerbehebungen + +- Button zum Speichern erscheint nicht mehr ungwünscht beim Verschieben des Detail-Fensters (36bc8ad) +- Das Detail-Fenster wächst nicht mehr größer als die gesamte QGIS-Anwendung (4c34a81) +- fehlendes Vorschaubild für Klasse `BP_AnpflanzungBindungErhaltung` hinzugefügt (87ebf86) +- Objektbaum wird nach dem Hinzufügen neuer Objekte korrekt aktualisiert (d9288ac) +- fehlerhafte Validierung der Gemeinde-Auswahl korrigiert (4b974a9) +- Fehler behoben, durch den es möglich war, die Fenstergröße kleiner als den Inhalt anzupassen (d4aa56a) + +### [1.4.2] - 07.02.2022 + +#### Neue Funktionen + +- berechnungsintensive Funktionen blockieren nicht mehr die Benutzeroberfläche +- Exportfunktion unterstützt auch Punktgeometrien +- neue Einstellungs-Option zum Anpassen des Exportpfades für externe Referenzen + +#### Veränderungen + +- XPlanung - Map Tool lässt sich besser deaktivieren (c55fa60c) +- Nutzung der QGIS - Einstellungen zum Speichern von Datenbankinformationen etc. +- Dialog für Einstellungen überarbeitet (bessere Beschriftungen von Labels und Buttons, besserer Gruppierung der Menüpunkte) +- Exportfunktion überprüft Dateiname auf unerlaubte Zeichen und entfernt diese (e481e512) + +#### Fehlerbehebungen + +- bei sich überlagernden Geometrien wird die korrekte Auswahl identifiziert (dd2e09bd) +- Attribut `flaechenschluss` ist nicht mehr sichtbar beim Erfassen von Punktgeometrien (c1c78c8e) +- Planwerke werden beim Wechseln der Datenbank aktualisiert (4ef0551) +- PostGIS Verbindungen werden in bestimmten Fällen besser erkannt (da091820) + +### [1.4.1] - 21.01.2022 + +#### Neue Funktionen + +- Kartenwerkzeug zum Konfigurieren und Abfragen von Planinhalten + +#### Veränderungen + +- keine Temporärlayer-Warnung für XPlanung-Layer (46ceba9) +- Auswahl von Objekten aus sich überschneidenden Layer möglich (897c36d) + +#### Fehlerbehebungen + +- Fehler beim Erstellen von Planwerken wenn kein Layer vorhanden war behoben (2379b5e) +- fehlerhafte Vorschauwerte bei der Bearbeitung von Auswahllisten entfernt (4ef0551) +- Fehler beim Import von FNP's behoben (2336652) + +### [1.4.0] - 21.01.2022 + +#### Neue Funktionen + +- neue Objektarten: + - `BP_BauLinie`, `BP_BesondererNutzungszweckFlaeche`, `BP_GemeinbedarfsFlaeche`, + `BP_SpielSportanlagenFlaeche`, `BP_GruenFlaeche`, `BP_LandwirtschaftsFlaeche`, + `BP_WaldFlaeche`, `BP_StrassenVerkehrsFlaeche`, `BP_GewaesserFlaeche`, + `BP_StrassenbegrenzungsLinie`, `BP_VerkehrsflaecheBesondererZweckbestimmung`, + `BP_VerEntsorgung`, `BP_AnpflanzungBindungErhaltung` + +#### Veränderungen + +- Import von XPlanGML-Dokumenten für Multipart-Geometrien `MultiSurface` und `MultiCurve` (728dc80) +- Darstellung aller Objektarten überarbeitet: Symbolisierung passt sich der Zweckbestimmung von Objekten an + +#### Fehlerbehebungen + +- Attribut `flaechenschluss` ist nicht mehr sichtbar beim Erfassen von Liniengeometrien (971aa5e) +- Fehler behoben, der bewirkte, dass das Detail-Fenster nicht erschien (a566df4) +- Fehler beim Abbruch der Bestätigung zum Löschen von Objekten behoben (0063126) + +### [1.3.1] - 07.01.2022 + +#### Neue Funktionen + +- Geometrievalidierung: + - Prüfung doppelter Stützpunkte + - Prüfung des Flächenschluss (Überschneidungen von Planinhalten, Geometrien außerhalb des Geltungsbereich und fehlende Flächen) + - alle Geometriefehler können auf der Karte markiert werden +- Funktion zum Erzwingen des Flächenschluss (füllt Flächen ohne geplante Nutzung) +- Anpassungen von Geometrien der Planinhalte über QGIS-Funktionen wird in die Datenbank übernommen +- Funktion zum Erfassen von Datenobjekten (bspw. `XP_ExterneReferenz`) aus dem Objektbaum + +#### Veränderungen + +- Dialoge für einzelne Pläne sind jetzt ein `DockWidget` wie das Hauptfenster (5c56991) +- Größere Eingabefelder für mehrzeilige Kommentare (f840f88) +- Geometrie wird als besser lesbares WKT anstatt WKB angezeigt (6f859d3) + +#### Fehlerbehebungen + +- `rechtscharakter` ist jetzt eine notwendige Angabe (5c25f5d) +- Anzeige von Auswahlwerten korrigiert (df4555f) +- Indikatoren im Layerbaum werden beim verschieben der Knoten erhalten (18d9c3d) + +### [1.3.0] - 29.11.2021 + +#### Neue Funktionen + +- Schaltfläche zum Aktualisieren eines geladenen Planwerks im LayerTree + +#### Veränderungen + +- bessere Validierung bei der Zuordnung von Planinhalten zu Planwerken ohne Bereich +- Auswahl von Geometrien erlaubt neben 'normalen' Geometrien, jetzt auch `CurvePolygon`, `CompoundCurve` und `MultiCurve` + +#### Fehlerbehebungen + +- Falsche Anzeige von Layer-Symbolisierungen, bei Layern mit unterschiedlichen XPlanung-Typen +- kritischer Fehler behoben, der Abstürze beim (zweiten) Speichern von Projekten verursachte +- Fehler behoben, bei dem nur abwechselnd Symbole oder Text-Annotationen angezeigt wurden + +### [1.2.0] - 22.11.2021 + +> Version enthält einen kritischen Fehler, der QGIS beim Speichern von Projekten zum Absturz bringt! +> Alle geänderten Projektinhalte gehen dabei verloren! + +#### Neue Funktionen + +- vollvektorielle Erfassung von Planinhalten +- Bearbeiten und Löschen von erfassten Planinhalten +- Planwerk mit Präsentationsobjekten (Text oder Symbole) annotieren + +#### Veränderungen + +- weitere Validierung der Konformitätsbedingungen +- einige Verbesserungen der Benutzeroberfläche durch passendere Widgets, mehr Tooltips und Hinweise +- bessere Validierung von fehlerhaften Eingaben + +#### Fehlerbehebungen + +- Fehler beim Exportieren von Enum-Werten +- Anzeigefehler beim Laden von Liniengeometrien +- fehlerhafte Deaktivierung von Schaltflächen +- Doppeltes Laden von Rasterbildern +- Absturz beim Rendern von FNP's + +### [1.1.0] - 24.08.2021 + +#### Neue Funktionen + +- teilvektorielle Erfassung von Planinhalten für BPläne und FNP +- Verknüpfung von externen Referenzen mit Planwerken +- Anzeige erfasster Planwerke auf der QGIS-Karte diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..693fddc --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# SAGis XPlanung + +SAGis XPlanung ist eine Erweiterung für die GIS-Software QGIS zur [XPlanung](https://www.xleitstelle.de/xplanung/ueber_xplanung) - konformen +Erfassung von Flächennutzungsplänen, Bebauungsplänen und Raumordnungsplänen. + +## Funktionen + +- Digitale Erfassung von Planwerken der Bauleitplanung (teil- und vollvektorielle Erfassungsstrategie) +- Persistentes Speichern der Planinhalte in einer Datenbank +- Verknüpfung der räumlichen Planinhalte mit externen Referenzen +- Export des Austauschformats _XPlanGML_ (Version 5.3 und 6.0) +- Import von _XPlanGML_-Dokumenten +- Visualisierung der räumlichen Informationen von Planwerken auf dem QGIS-Kartencanvas + +> [!NOTE] +> Die hier bereitgestellte Version besitzt nicht den vollen Funktionsumfang. Bitte beachten Sie die folgende Tabelle zur Gegenüberstellung der Versionen. + +| | Community-Version | Vollversion | +|--------------------------------------------------------------|--------------------|--------------------| +| Teilvektorielle Erfassung | :heavy_check_mark: | :heavy_check_mark: | +| Speichern externer Referenzen | :heavy_check_mark: | :heavy_check_mark: | +| Import von XPlanGML-Dokumenten | :heavy_check_mark: | :heavy_check_mark: | +| Visualisierung gemäß PlanZV | :heavy_check_mark: | :heavy_check_mark: | +| Export von XPlanGML-Dokumenten | [^abbr1] | :heavy_check_mark: | +| Bearbeiten von XPlanung-Attributen | [^abbr1] | :heavy_check_mark: | +| Erfassung neuer Planinhalte
(vollvektorielle Erfassung) | | :heavy_check_mark: | +[^abbr1]: Nur für Basisobjekte möglich (teilvektorielle Erfassung) + + +## Nutzung + +### Software-Vorraussetzungen + +- QGIS >= 3.22 +- PostgreSQL >= 9.6 mit PostGIS 3.1 + +### Installation + +SAGis XPlanung kann über das QGIS-Plugin Repository heruntergeladen werden. + +Für eine erfolgreiche Ausführung des Programms müssen zudem folgende Python-Komponenten installiert werden: +- SQLAlchemy==1.4.49 (:warning: Anwendung nicht kompatibel mit SQLAlchemy 2.0) +- GeoAlchemy2==0.12.5 +- lxml==4.6.3 +- shapely==2.0.0 +- qasync==0.22.0 +- asyncpg==0.26.0 + +
Anleitung anzeigen + +1. Suchen Sie das Installationsverzeichnis von QGIS (Zumeist `C:\OSGeo4W\` oder `C:\Program Files\QGIS 3.*'`) + +2. Im Verzeichnis befindet sich die _OSGeo4W-Shell_ (Datei mit dem Namen `OSGeo4W.bat`). Starten Sie die _OSGeo4W-Shell_ und führen Sie den folgenden Befehl im sich öffnenden Programm aus: + + ```sh + o4w_env & python3 -m pip install sqlalchemy==1.4.46 GeoAlchemy2 qasync shapely==2.0.0 lxml asyncpg + ``` + +
+ +### Beispieldaten + +> [!NOTE] +> Zum Testen stellt die [Leitstelle XPlanung/XBau](https://xleitstelle.de) im folgenden Repository Testdaten im XPlanGML-Format zur Verfügung: https://bitbucket.org/geowerkstatt-hamburg/xplan-testdaten/src/master/ + +
Visualisierung Beispielplan mit SAGis XPlanung +
+ + +
+
+ diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..d82e5bd --- /dev/null +++ b/alembic.ini @@ -0,0 +1,100 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. Valid values are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # default: use os.pathsep + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = postgresql:// + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = DEBUG +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = DEBUG +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/alembic/env.py b/alembic/env.py new file mode 100644 index 0000000..dab6a88 --- /dev/null +++ b/alembic/env.py @@ -0,0 +1,104 @@ +import os +import sys +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from geoalchemy2 import alembic_helpers + +from alembic import context + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + +# import all XPlanung Classes to load models +from SAGisXPlanung.utils import CLASSES +from SAGisXPlanung import Base + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = Base.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + +# def include_object(object, name, type_, reflected, compare_to): +# if type_ == "table" and name == 'spatial_ref_sys': +# return False +# else: +# return True + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + process_revision_directives=alembic_helpers.writer, + render_item=alembic_helpers.render_item, + # include_object=include_object + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix="sqlalchemy.", + poolclass=pool.NullPool + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=alembic_helpers.writer, + render_item=alembic_helpers.render_item, + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + config.set_main_option('sqlalchemy.url', os.environ['DB_URI']) + run_migrations_online() diff --git a/alembic/script.py.mako b/alembic/script.py.mako new file mode 100644 index 0000000..41b5eb1 --- /dev/null +++ b/alembic/script.py.mako @@ -0,0 +1,35 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +import os +import sys + +from alembic import op +import sqlalchemy as sa + +${imports if imports else ""} + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/alembic/versions/0a6f00a1f663_v1_9_0.py b/alembic/versions/0a6f00a1f663_v1_9_0.py new file mode 100644 index 0000000..33fc447 --- /dev/null +++ b/alembic/versions/0a6f00a1f663_v1_9_0.py @@ -0,0 +1,116 @@ +"""v1.9.0 + +Revision ID: 0a6f00a1f663 +Revises: 812b33dce3d1 +Create Date: 2023-01-12 12:38:21.165151 + +""" +import os +import sys + +from alembic import op, context +import sqlalchemy as sa + +from geoalchemy2 import Geometry +from sqlalchemy.dialects import postgresql + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + +# revision identifiers, used by Alembic. +revision = '0a6f00a1f663' +down_revision = '812b33dce3d1' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute("UPDATE xp_po SET type='xp_pto' WHERE type='xp_tpo';") + + op.execute("ALTER INDEX idx_xp_tpo_position RENAME TO idx_xp_pto_position;") + + op.execute("ALTER TABLE xp_tpo RENAME CONSTRAINT xp_tpo_id_fkey TO xp_pto_id_fkey;") + op.execute("ALTER TABLE xp_tpo RENAME CONSTRAINT xp_tpo_pkey TO xp_pto_pkey;") + + op.execute("ALTER TABLE xp_tpo RENAME TO xp_pto;") + + # change gml:AngleType columns to float + op.execute('ALTER TABLE xp_ppo ALTER COLUMN drehwinkel TYPE FLOAT USING drehwinkel::float;') + op.execute('ALTER TABLE xp_pto ALTER COLUMN drehwinkel TYPE FLOAT USING drehwinkel::float;') + op.execute('ALTER TABLE xp_nutzungsschablone ALTER COLUMN drehwinkel TYPE FLOAT USING drehwinkel::float;') + op.execute('ALTER TABLE xp_objekt ALTER COLUMN drehwinkel TYPE FLOAT USING drehwinkel::float;') + + # update inheritance hierarchy + op.execute( + 'INSERT INTO xp_externe_referenz (id, datum, "referenzMimeType", art, "referenzName", beschreibung, file, "georefURL", "referenzURL") ' + 'SELECT id, datum, "referenzMimeType", art, "referenzName", beschreibung, file, "georefURL", "referenzURL" FROM xp_spez_externe_referenz') + op.create_foreign_key('xp_spez_externe_referenz_id_fkey', 'xp_spez_externe_referenz', 'xp_externe_referenz', ['id'], + ['id'], ondelete='CASCADE') + op.drop_column('xp_spez_externe_referenz', 'datum') + op.drop_column('xp_spez_externe_referenz', 'referenzMimeType') + op.drop_column('xp_spez_externe_referenz', 'art') + op.drop_column('xp_spez_externe_referenz', 'referenzName') + op.drop_column('xp_spez_externe_referenz', 'beschreibung') + op.drop_column('xp_spez_externe_referenz', 'file') + op.drop_column('xp_spez_externe_referenz', 'georefURL') + op.drop_column('xp_spez_externe_referenz', 'referenzURL') + op.add_column('xp_externe_referenz', sa.Column('type', sa.String(length=50), nullable=True)) + op.execute("UPDATE xp_externe_referenz SET type='xp_spez_externe_referenz' WHERE id in (SELECT id FROM xp_spez_externe_referenz);") + op.execute("UPDATE xp_externe_referenz SET type='xp_externe_referenz' WHERE type IS NULL") + + +def downgrade(): + # change gml:AngleType columns back to int + op.execute('ALTER TABLE xp_ppo ALTER COLUMN drehwinkel TYPE integer USING drehwinkel::integer;') + op.execute('ALTER TABLE xp_pto ALTER COLUMN drehwinkel TYPE integer USING drehwinkel::integer;') + op.execute('ALTER TABLE xp_nutzungsschablone ALTER COLUMN drehwinkel TYPE integer USING drehwinkel::integer;') + op.execute('ALTER TABLE xp_objekt ALTER COLUMN drehwinkel TYPE integer USING drehwinkel::integer;') + + # rename pto back to tpo + op.execute("UPDATE xp_po SET type='xp_tpo' WHERE type='xp_pto';") + + op.execute("ALTER INDEX idx_xp_pto_position RENAME TO idx_xp_tpo_position;") + + op.execute("ALTER TABLE xp_pto RENAME CONSTRAINT xp_pto_id_fkey TO xp_tpo_id_fkey;") + op.execute("ALTER TABLE xp_pto RENAME CONSTRAINT xp_pto_pkey TO xp_tpo_pkey;") + + op.execute("ALTER TABLE xp_pto RENAME TO xp_tpo;") + + # revert inheritance hierarchy of xp_externereferenz + xp_externereferenzart_enum = postgresql.ENUM('Dokument', 'PlanMitGeoreferenz', name='xp_externereferenzart', + create_type=False) + referenzMimeType_enum = postgresql.ENUM('application/pdf', 'application/zip', 'application/xml', + 'application/msword', 'application/msexcel', + 'application/vnd.ogc.sld+xml', 'application/vnd.ogc.wms_xml', + 'application/vnd.ogc.gml', 'application/vnd.shp', + 'application/vnd.dbf', 'application/vnd.shx', + 'application/octet-stream', 'image/vnd.dxf', 'image/vnd.dwg', + 'image/jpg', 'image/png', 'image/tiff', 'image/bmp', 'image/ecw', + 'image/svg+xml', 'text/html', 'text/plain', + name='xp_mime_types', create_type=False) + if not context.is_offline_mode(): + xp_externereferenzart_enum.create(op.get_bind(), checkfirst=True) + referenzMimeType_enum.create(op.get_bind(), checkfirst=True) + + op.add_column('xp_spez_externe_referenz', + sa.Column('referenzURL', sa.VARCHAR(), autoincrement=False, nullable=True)) + op.add_column('xp_spez_externe_referenz', sa.Column('georefURL', sa.VARCHAR(), autoincrement=False, nullable=True)) + op.add_column('xp_spez_externe_referenz', sa.Column('file', postgresql.BYTEA(), autoincrement=False, nullable=True)) + op.add_column('xp_spez_externe_referenz', + sa.Column('beschreibung', sa.VARCHAR(), autoincrement=False, nullable=True)) + op.add_column('xp_spez_externe_referenz', + sa.Column('referenzName', sa.VARCHAR(), autoincrement=False, nullable=True)) + op.add_column('xp_spez_externe_referenz', + sa.Column('art', xp_externereferenzart_enum, autoincrement=False, nullable=True)) + op.add_column('xp_spez_externe_referenz', + sa.Column('referenzMimeType', referenzMimeType_enum, autoincrement=False, nullable=True)) + op.add_column('xp_spez_externe_referenz', sa.Column('datum', sa.DATE(), autoincrement=False, nullable=True)) + op.drop_constraint('xp_spez_externe_referenz_id_fkey', 'xp_spez_externe_referenz', type_='foreignkey') + + op.execute('UPDATE xp_spez_externe_referenz spez SET datum=o.datum, "referenzMimeType"=o."referenzMimeType", art=o.art, "referenzName"=o."referenzName", beschreibung=o.beschreibung, file=o.file, "georefURL"=o."georefURL", "referenzURL"=o."referenzURL" ' + 'FROM xp_externe_referenz o WHERE spez.id=o.id;') + op.execute('DELETE FROM xp_externe_referenz WHERE id in (SELECT id FROM xp_spez_externe_referenz);') + op.drop_column('xp_externe_referenz', 'type') diff --git a/alembic/versions/1a015621a38f_v1_7_0.py b/alembic/versions/1a015621a38f_v1_7_0.py new file mode 100644 index 0000000..19389f3 --- /dev/null +++ b/alembic/versions/1a015621a38f_v1_7_0.py @@ -0,0 +1,223 @@ +"""v1.7.0 + +Revision ID: 1a015621a38f +Revises: 873e18b73036 +Create Date: 2022-10-05 09:41:30.415953 + +""" +import os +import sys + +from alembic import op, context +import sqlalchemy as sa + +from geoalchemy2 import Geometry +from sqlalchemy.dialects import postgresql + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + +# revision identifiers, used by Alembic. +revision = '1a015621a38f' +down_revision = '873e18b73036' +branch_labels = None +depends_on = None + + +def upgrade(): + allgartderbaulnutzung_enum = postgresql.ENUM('WohnBauflaeche', 'GemischteBauflaeche', 'GewerblicheBauflaeche', + 'SonderBauflaeche', 'Sonstiges', name='xp_allgartderbaulnutzung', + create_type=False) + besondereartderbaulnutzung_enum = postgresql.ENUM('Kleinsiedlungsgebiet', 'ReinesWohngebiet', 'AllgWohngebiet', + 'BesonderesWohngebiet', 'Dorfgebiet', 'Mischgebiet', + 'UrbanesGebiet', + 'Kerngebiet', 'Gewerbegebiet', 'Industriegebiet', + 'SondergebietErholung', + 'SondergebietSonst', 'Wochenendhausgebiet', 'Sondergebiet', + 'SonstigesGebiet', + name='xp_besondereartderbaulnutzung', create_type=False) + + sondernutzung_enum = postgresql.ENUM('KeineSondernutzung', 'Wochendhausgebiet', 'Ferienhausgebiet', + 'Campingplatzgebiet', + 'Kurgebiet', 'SonstSondergebietErholung', 'Einzelhandelsgebiet', + 'GrossflaechigerEinzelhandel', 'Ladengebiet', 'Einkaufszentrum', + 'SonstGrossflEinzelhandel', 'Verkehrsuebungsplatz', 'Hafengebiet', + 'SondergebietErneuerbareEnergie', 'SondergebietMilitaer', + 'SondergebietLandwirtschaft', + 'SondergebietSport', 'SondergebietGesundheitSoziales', 'Klinikgebiet', + 'Golfplatz', + 'SondergebietKultur', 'SondergebietTourismus', + 'SondergebietBueroUndVerwaltung', + 'SondergebietJustiz', 'SondergebietHochschuleForschung', 'SondergebietMesse', + 'SondergebietAndereNutzungen', name='xp_sondernutzungen', create_type=False) + + zweckbestimmung_enum = postgresql.ENUM('OeffentlicheVerwaltung', 'KommunaleEinrichtung', + 'BetriebOeffentlZweckbestimmung', 'AnlageBundLand', + 'BildungForschung', 'Schule', 'Hochschule', + 'BerufsbildendeSchule', 'Forschungseinrichtung', 'Kirche', + 'Sakralgebaeude', 'KirchlicheVerwaltung', 'Kirchengemeinde', + 'Sozial', 'EinrichtungKinder', 'EinrichtungJugendliche', + 'EinrichtungFamilienErwachsene', 'EinrichtungSenioren', + 'SonstigeSozialeEinrichtung', 'EinrichtungBehinderte', + 'Gesundheit', 'Krankenhaus', 'Kultur', 'MusikTheater', + 'Bildung', 'Sport', 'Bad', 'SportplatzSporthalle', + 'SicherheitOrdnung', 'Feuerwehr', 'Schutzbauwerk', 'Justiz', + 'Infrastruktur', 'Post', 'Sonstiges', + name='xp_zweckbestimmunggemeinbedarf', create_type=False) + + zweck_gruen_enum = postgresql.ENUM('Parkanlage', 'ParkanlageHistorisch', 'ParkanlageNaturnah', + 'ParkanlageWaldcharakter', 'NaturnaheUferParkanlage', + 'Dauerkleingarten', 'ErholungsGaerten', 'Sportplatz', + 'Reitsportanlage', 'Hundesportanlage', 'Wassersportanlage', + 'Schiessstand', 'Golfplatz', 'Skisport', 'Tennisanlage', + 'Spielplatz', 'Bolzplatz', 'Abenteuerspielplatz', 'Zeltplatz', + 'Campingplatz', 'Badeplatz', 'FreizeitErholung', + 'Kleintierhaltung', 'Festplatz', 'SpezGruenflaeche', + 'StrassenbegleitGruen', 'BoeschungsFlaeche', 'FeldWaldWiese', + 'Uferschutzstreifen', 'Abschirmgruen', + 'UmweltbildungsparkSchaugatter', 'RuhenderVerkehr', 'Friedhof', + 'Sonstiges', 'Gaertnerei', name='xp_zweckbestimmunggruen', create_type=False) + + zweck_wasser_enum = postgresql.ENUM('Hafen', 'Sportboothafen', 'Wasserflaeche', 'Fliessgewaesser', 'Sonstiges', + name='xp_zweckbestimmunggewaesser', create_type=False) + nutzungsform_enum = postgresql.ENUM('Privat', 'Oeffentlich', name='xp_nutzungsform', create_type=False) + betretung_wald_enum = postgresql.ENUM('KeineZusaetzlicheBetretung', 'Radfahren', 'Reiten', 'Fahren', 'Hundesport', + 'Sonstiges', name='xp_waldbetretungtyp', create_type=False) + eigentum_wald_enum = postgresql.ENUM('OeffentlicherWald', 'Staatswald', 'Koerperschaftswald', 'Kommunalwald', + 'Stiftungswald', 'Privatwald', 'Gemeinschaftswald', 'Genossenschaftswald', + 'Kirchenwald', 'Sonstiges', name='xp_eigentumsartwald', create_type=False) + zweck_wald_enum = postgresql.ENUM('Naturwald', 'Waldschutzgebiet', 'Nutzwald', 'Erholungswald', 'Schutzwald', + 'Bodenschutzwald', 'Biotopschutzwald', 'NaturnaherWald', + 'SchutzwaldSchaedlicheUmwelteinwirkungen', 'Schonwald', 'Bannwald', + 'FlaecheForstwirtschaft', 'ImmissionsgeschaedigterWald', 'Sonstiges', + name='xp_zweckbestimmungwald', create_type=False) + zweck_sport_enum = postgresql.ENUM('Sportanlage', 'Spielanlage', 'SpielSportanlage', 'Sonstiges', + name='xp_zweckbestimmungspielsportanlage', create_type=False) + zweck_landwirtschaft_enum = postgresql.ENUM('LandwirtschaftAllgemein', 'Ackerbau', 'WiesenWeidewirtschaft', + 'GartenbaulicheErzeugung', 'Obstbau', 'Weinbau', 'Imkerei', + 'Binnenfischerei', 'Sonstiges', + name='xp_zweckbestimmunglandwirtschaft', create_type=False) + + if not context.is_offline_mode(): + allgartderbaulnutzung_enum.create(op.get_bind(), checkfirst=True) + besondereartderbaulnutzung_enum.create(op.get_bind(), checkfirst=True) + sondernutzung_enum.create(op.get_bind(), checkfirst=True) + zweckbestimmung_enum.create(op.get_bind(), checkfirst=True) + zweck_gruen_enum.create(op.get_bind(), checkfirst=True) + zweck_wasser_enum.create(op.get_bind(), checkfirst=True) + nutzungsform_enum.create(op.get_bind(), checkfirst=True) + zweck_landwirtschaft_enum.create(op.get_bind(), checkfirst=True) + zweck_sport_enum.create(op.get_bind(), checkfirst=True) + zweck_wald_enum.create(op.get_bind(), checkfirst=True) + eigentum_wald_enum.create(op.get_bind(), checkfirst=True) + + op.create_geospatial_table('fp_objekt', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('rechtscharakter', + sa.Enum('Darstellung', 'NachrichtlicheUebernahme', 'Hinweis', 'Vermerk', + 'Kennzeichnung', 'Unbekannt', name='fp_rechtscharakter'), + nullable=False), + sa.Column('vonGenehmigungAusgenommen', sa.Boolean(), nullable=True), + sa.Column('position', + Geometry(spatial_index=False, from_text='ST_GeomFromEWKT', name='geometry'), + nullable=True), + sa.Column('flaechenschluss', sa.Boolean(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['xp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_geospatial_index('idx_fp_objekt_position', 'fp_objekt', ['position'], unique=False, + postgresql_using='gist', postgresql_ops={}) + op.create_table('fp_baugebiet', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('GFZ', sa.Float(), nullable=True), + sa.Column('GFZmin', sa.Float(), nullable=True), + sa.Column('GFZmax', sa.Float(), nullable=True), + sa.Column('BMZ', sa.Float(), nullable=True), + sa.Column('GRZ', sa.Float(), nullable=True), + sa.Column('allgArtDerBaulNutzung', allgartderbaulnutzung_enum, nullable=True), + sa.Column('besondereArtDerBaulNutzung', besondereartderbaulnutzung_enum, nullable=True), + sa.Column('sonderNutzung', sa.ARRAY(sondernutzung_enum), nullable=True), + sa.Column('nutzungText', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['fp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('fp_gemeinbedarf', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('zweckbestimmung', zweckbestimmung_enum, nullable=True), + sa.ForeignKeyConstraint(['id'], ['fp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('fp_gewaesser', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('zweckbestimmung', zweck_wasser_enum, nullable=True), + sa.ForeignKeyConstraint(['id'], ['fp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('fp_gruen', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('zweckbestimmung', zweck_gruen_enum, nullable=True), + sa.Column('nutzungsform', nutzungsform_enum, nullable=True), + sa.ForeignKeyConstraint(['id'], ['fp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('fp_landwirtschaft', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('zweckbestimmung', zweck_landwirtschaft_enum, nullable=True), + sa.ForeignKeyConstraint(['id'], ['fp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('fp_spiel_sportanlage', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('zweckbestimmung', zweck_sport_enum, nullable=True), + sa.ForeignKeyConstraint(['id'], ['fp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('fp_strassenverkehr', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('zweckbestimmung', sa.Enum('Autobahn', 'Hauptverkehrsstrasse', 'Ortsdurchfahrt', + 'SonstigerVerkehrswegAnlage', 'VerkehrsberuhigterBereich', + 'Platz', 'Fussgaengerbereich', 'RadGehweg', 'Radweg', 'Gehweg', + 'Wanderweg', 'ReitKutschweg', 'Rastanlage', 'Busbahnhof', + 'UeberfuehrenderVerkehrsweg', 'UnterfuehrenderVerkehrsweg', + 'Wirtschaftsweg', 'LandwirtschaftlicherVerkehr', + 'RuhenderVerkehr', 'Parkplatz', 'FahrradAbstellplatz', + 'P_RAnlage', 'CarSharing', 'BikeSharing', 'B_RAnlage', + 'Parkhaus', 'Mischverkehrsflaeche', 'Ladestation', 'Sonstiges', + name='fp_zweckbestimmungstrassenverkehr'), nullable=True), + sa.Column('nutzungsform', nutzungsform_enum, nullable=True), + sa.ForeignKeyConstraint(['id'], ['fp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('fp_wald', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('zweckbestimmung', zweck_wald_enum, nullable=True), + sa.Column('eigentumsart', eigentum_wald_enum, nullable=True), + sa.Column('betreten', sa.ARRAY(betretung_wald_enum), nullable=True), + sa.ForeignKeyConstraint(['id'], ['fp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + + +def downgrade(): + op.execute("DELETE FROM xp_objekt CASCADE WHERE type in ('fp_wald', 'fp_strassenverkehr', 'fp_spiel_sportanlage', " + "'fp_landwirtschaft', 'fp_gruen', 'fp_gewaesser', 'fp_gemeinbedarf', " + "'fp_baugebiet');") + + op.drop_table('fp_wald') + op.drop_table('fp_strassenverkehr') + op.drop_table('fp_spiel_sportanlage') + op.drop_table('fp_landwirtschaft') + op.drop_table('fp_gruen') + op.drop_table('fp_gewaesser') + op.drop_table('fp_gemeinbedarf') + op.drop_table('fp_baugebiet') + op.drop_geospatial_index('idx_fp_objekt_position', table_name='fp_objekt', postgresql_using='gist', + column_name='position') + op.drop_geospatial_table('fp_objekt') + + op.execute("DROP TYPE fp_zweckbestimmungstrassenverkehr CASCADE;") + op.execute("DROP TYPE fp_rechtscharakter CASCADE;") + # ### end Alembic commands ### diff --git a/alembic/versions/20c50b38b0af_v1_3_1.py b/alembic/versions/20c50b38b0af_v1_3_1.py new file mode 100644 index 0000000..e7bdb92 --- /dev/null +++ b/alembic/versions/20c50b38b0af_v1_3_1.py @@ -0,0 +1,50 @@ +"""v1.3.1 + +Revision ID: 20c50b38b0af +Revises: cfbf4d3b2a9d +Create Date: 2022-01-07 09:39:09.632579 + +""" +import os +import sys +from geoalchemy2 import Geometry +from alembic import op +import sqlalchemy as sa + +from sqlalchemy.dialects import postgresql + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + +# revision identifiers, used by Alembic. +revision = '20c50b38b0af' +down_revision = 'cfbf4d3b2a9d' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table('bp_flaeche_ohne_festsetzung', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.alter_column('bp_objekt', 'rechtscharakter', + existing_type=postgresql.ENUM('Festsetzung', 'NachrichtlicheUebernahme', 'Hinweis', 'Vermerk', + 'Kennzeichnung', 'Unbekannt', name='bp_rechtscharakter'), + nullable=False) + op.drop_column('xp_bereich', 'srs') + op.drop_column('xp_plan', 'srs') + + +def downgrade(): + op.add_column('xp_plan', sa.Column('srs', sa.VARCHAR(length=20), autoincrement=False, nullable=True)) + op.add_column('xp_bereich', sa.Column('srs', sa.VARCHAR(length=20), autoincrement=False, nullable=True)) + op.alter_column('bp_objekt', 'rechtscharakter', + existing_type=postgresql.ENUM('Festsetzung', 'NachrichtlicheUebernahme', 'Hinweis', 'Vermerk', + 'Kennzeichnung', 'Unbekannt', name='bp_rechtscharakter'), + nullable=True) + op.drop_table('bp_flaeche_ohne_festsetzung') diff --git a/alembic/versions/2cee32cfc646_v1_4_0.py b/alembic/versions/2cee32cfc646_v1_4_0.py new file mode 100644 index 0000000..e53cfc4 --- /dev/null +++ b/alembic/versions/2cee32cfc646_v1_4_0.py @@ -0,0 +1,538 @@ +"""v1.4.0 + +Revision ID: 2cee32cfc646 +Revises: 20c50b38b0af +Create Date: 2022-01-21 09:23:22.275548 + +""" +import os +import sys + +from alembic import op, context +import sqlalchemy as sa + +from sqlalchemy.dialects import postgresql + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + +# revision identifiers, used by Alembic. +revision = '2cee32cfc646' +down_revision = '20c50b38b0af' +branch_labels = None +depends_on = None + + +def upgrade(): + bauweise_enum = postgresql.ENUM('KeineAngabe', 'OffeneBauweise', 'GeschlosseneBauweise', 'AbweichendeBauweise', + name='bp_bauweise', create_type=False) + bebauungsart_enum = postgresql.ENUM('Einzelhaeuser', 'Doppelhaeuser', 'Hausgruppen', 'EinzelDoppelhaeuser', + 'EinzelhaeuserHausgruppen', 'DoppelhaeuserHausgruppen', 'Reihenhaeuser', + 'EinzelhaeuserDoppelhaeuserHausgruppen', name='bp_bebauungsart', + create_type=False) + if not context.is_offline_mode(): + bauweise_enum.create(op.get_bind(), checkfirst=True) + bebauungsart_enum.create(op.get_bind(), checkfirst=True) + + op.create_table('bp_baulinie', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('bautiefe', sa.Float(), nullable=True), + sa.Column('geschossMin', sa.Integer(), nullable=True), + sa.Column('geschossMax', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_besondere_nutzung', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('FR', sa.Float(), nullable=True), + sa.Column('MaxZahlWohnungen', sa.Integer(), nullable=True), + sa.Column('MinGRWohneinheit', sa.Float(), nullable=True), + sa.Column('Fmin', sa.Float(), nullable=True), + sa.Column('Fmax', sa.Float(), nullable=True), + sa.Column('Bmin', sa.Float(), nullable=True), + sa.Column('Bmax', sa.Float(), nullable=True), + sa.Column('Tmin', sa.Float(), nullable=True), + sa.Column('Tmax', sa.Float(), nullable=True), + sa.Column('GFZmin', sa.Float(), nullable=True), + sa.Column('GFZmax', sa.Float(), nullable=True), + sa.Column('GFZ', sa.Float(), nullable=True), + sa.Column('GFZ_Ausn', sa.Float(), nullable=True), + sa.Column('GFmin', sa.Float(), nullable=True), + sa.Column('GFmax', sa.Float(), nullable=True), + sa.Column('GF', sa.Float(), nullable=True), + sa.Column('GF_Ausn', sa.Float(), nullable=True), + sa.Column('BMZ', sa.Float(), nullable=True), + sa.Column('BMZ_Ausn', sa.Float(), nullable=True), + sa.Column('BM', sa.Float(), nullable=True), + sa.Column('BM_Ausn', sa.Float(), nullable=True), + sa.Column('GRZmin', sa.Float(), nullable=True), + sa.Column('GRZmax', sa.Float(), nullable=True), + sa.Column('GRZ', sa.Float(), nullable=True), + sa.Column('GRZ_Ausn', sa.Float(), nullable=True), + sa.Column('GRmin', sa.Float(), nullable=True), + sa.Column('GRmax', sa.Float(), nullable=True), + sa.Column('GR', sa.Float(), nullable=True), + sa.Column('GR_Ausn', sa.Float(), nullable=True), + sa.Column('Zmin', sa.Integer(), nullable=True), + sa.Column('Zmax', sa.Integer(), nullable=True), + sa.Column('Zzwingend', sa.Integer(), nullable=True), + sa.Column('Z', sa.Integer(), nullable=True), + sa.Column('Z_Ausn', sa.Integer(), nullable=True), + sa.Column('Z_Staffel', sa.Integer(), nullable=True), + sa.Column('Z_Dach', sa.Integer(), nullable=True), + sa.Column('ZUmin', sa.Integer(), nullable=True), + sa.Column('ZUmax', sa.Integer(), nullable=True), + sa.Column('ZUzwingend', sa.Integer(), nullable=True), + sa.Column('ZU', sa.Integer(), nullable=True), + sa.Column('ZU_Ausn', sa.Integer(), nullable=True), + sa.Column('zweckbestimmung', sa.String(), nullable=True), + sa.Column('bauweise', bauweise_enum, nullable=True), + sa.Column('bebauungsArt', bebauungsart_enum, nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_gemeinbedarf', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('FR', sa.Integer(), nullable=True), + sa.Column('MaxZahlWohnungen', sa.Integer(), nullable=True), + sa.Column('MinGRWohneinheit', sa.Float(), nullable=True), + sa.Column('Fmin', sa.Float(), nullable=True), + sa.Column('Fmax', sa.Float(), nullable=True), + sa.Column('Bmin', sa.Float(), nullable=True), + sa.Column('Bmax', sa.Float(), nullable=True), + sa.Column('Tmin', sa.Float(), nullable=True), + sa.Column('Tmax', sa.Float(), nullable=True), + sa.Column('GFZmin', sa.Float(), nullable=True), + sa.Column('GFZmax', sa.Float(), nullable=True), + sa.Column('GFZ', sa.Float(), nullable=True), + sa.Column('GFZ_Ausn', sa.Float(), nullable=True), + sa.Column('GFmin', sa.Float(), nullable=True), + sa.Column('GFmax', sa.Float(), nullable=True), + sa.Column('GF', sa.Float(), nullable=True), + sa.Column('GF_Ausn', sa.Float(), nullable=True), + sa.Column('BMZ', sa.Float(), nullable=True), + sa.Column('BMZ_Ausn', sa.Float(), nullable=True), + sa.Column('BM', sa.Float(), nullable=True), + sa.Column('BM_Ausn', sa.Float(), nullable=True), + sa.Column('GRZmin', sa.Float(), nullable=True), + sa.Column('GRZmax', sa.Float(), nullable=True), + sa.Column('GRZ', sa.Float(), nullable=True), + sa.Column('GRZ_Ausn', sa.Float(), nullable=True), + sa.Column('GRmin', sa.Float(), nullable=True), + sa.Column('GRmax', sa.Float(), nullable=True), + sa.Column('GR', sa.Float(), nullable=True), + sa.Column('GR_Ausn', sa.Float(), nullable=True), + sa.Column('Zmin', sa.Integer(), nullable=True), + sa.Column('Zmax', sa.Integer(), nullable=True), + sa.Column('Zzwingend', sa.Integer(), nullable=True), + sa.Column('Z', sa.Integer(), nullable=True), + sa.Column('Z_Ausn', sa.Integer(), nullable=True), + sa.Column('Z_Staffel', sa.Integer(), nullable=True), + sa.Column('Z_Dach', sa.Integer(), nullable=True), + sa.Column('ZUmin', sa.Integer(), nullable=True), + sa.Column('ZUmax', sa.Integer(), nullable=True), + sa.Column('ZUzwingend', sa.Integer(), nullable=True), + sa.Column('ZU', sa.Integer(), nullable=True), + sa.Column('ZU_Ausn', sa.Integer(), nullable=True), + sa.Column('zweckbestimmung', sa.Enum('OeffentlicheVerwaltung', 'KommunaleEinrichtung', + 'BetriebOeffentlZweckbestimmung', 'AnlageBundLand', + 'BildungForschung', 'Schule', 'Hochschule', + 'BerufsbildendeSchule', 'Forschungseinrichtung', 'Kirche', + 'Sakralgebaeude', 'KirchlicheVerwaltung', 'Kirchengemeinde', + 'Sozial', 'EinrichtungKinder', 'EinrichtungJugendliche', + 'EinrichtungFamilienErwachsene', 'EinrichtungSenioren', + 'SonstigeSozialeEinrichtung', 'EinrichtungBehinderte', + 'Gesundheit', 'Krankenhaus', 'Kultur', 'MusikTheater', + 'Bildung', 'Sport', 'Bad', 'SportplatzSporthalle', + 'SicherheitOrdnung', 'Feuerwehr', 'Schutzbauwerk', 'Justiz', + 'Infrastruktur', 'Post', 'Sonstiges', + name='xp_zweckbestimmunggemeinbedarf'), nullable=True), + sa.Column('bauweise', bauweise_enum, nullable=True), + sa.Column('bebauungsArt', bebauungsart_enum , nullable=True), + sa.Column('zugunstenVon', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_gewaesser', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('zweckbestimmung', + sa.Enum('Hafen', 'Sportboothafen', 'Wasserflaeche', 'Fliessgewaesser', 'Sonstiges', + name='xp_zweckbestimmunggewaesser'), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_gruenflaeche', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('MaxZahlWohnungen', sa.Integer(), nullable=True), + sa.Column('MinGRWohneinheit', sa.Float(), nullable=True), + sa.Column('Fmin', sa.Float(), nullable=True), + sa.Column('Fmax', sa.Float(), nullable=True), + sa.Column('Bmin', sa.Float(), nullable=True), + sa.Column('Bmax', sa.Float(), nullable=True), + sa.Column('Tmin', sa.Float(), nullable=True), + sa.Column('Tmax', sa.Float(), nullable=True), + sa.Column('GFZmin', sa.Float(), nullable=True), + sa.Column('GFZmax', sa.Float(), nullable=True), + sa.Column('GFZ', sa.Float(), nullable=True), + sa.Column('GFZ_Ausn', sa.Float(), nullable=True), + sa.Column('GFmin', sa.Float(), nullable=True), + sa.Column('GFmax', sa.Float(), nullable=True), + sa.Column('GF', sa.Float(), nullable=True), + sa.Column('GF_Ausn', sa.Float(), nullable=True), + sa.Column('BMZ', sa.Float(), nullable=True), + sa.Column('BMZ_Ausn', sa.Float(), nullable=True), + sa.Column('BM', sa.Float(), nullable=True), + sa.Column('BM_Ausn', sa.Float(), nullable=True), + sa.Column('GRZmin', sa.Float(), nullable=True), + sa.Column('GRZmax', sa.Float(), nullable=True), + sa.Column('GRZ', sa.Float(), nullable=True), + sa.Column('GRZ_Ausn', sa.Float(), nullable=True), + sa.Column('GRmin', sa.Float(), nullable=True), + sa.Column('GRmax', sa.Float(), nullable=True), + sa.Column('GR', sa.Float(), nullable=True), + sa.Column('GR_Ausn', sa.Float(), nullable=True), + sa.Column('Zmin', sa.Integer(), nullable=True), + sa.Column('Zmax', sa.Integer(), nullable=True), + sa.Column('Zzwingend', sa.Integer(), nullable=True), + sa.Column('Z', sa.Integer(), nullable=True), + sa.Column('Z_Ausn', sa.Integer(), nullable=True), + sa.Column('Z_Staffel', sa.Integer(), nullable=True), + sa.Column('Z_Dach', sa.Integer(), nullable=True), + sa.Column('ZUmin', sa.Integer(), nullable=True), + sa.Column('ZUmax', sa.Integer(), nullable=True), + sa.Column('ZUzwingend', sa.Integer(), nullable=True), + sa.Column('ZU', sa.Integer(), nullable=True), + sa.Column('ZU_Ausn', sa.Integer(), nullable=True), + sa.Column('zweckbestimmung', sa.Enum('Parkanlage', 'ParkanlageHistorisch', 'ParkanlageNaturnah', + 'ParkanlageWaldcharakter', 'NaturnaheUferParkanlage', + 'Dauerkleingarten', 'ErholungsGaerten', 'Sportplatz', + 'Reitsportanlage', 'Hundesportanlage', 'Wassersportanlage', + 'Schiessstand', 'Golfplatz', 'Skisport', 'Tennisanlage', + 'Spielplatz', 'Bolzplatz', 'Abenteuerspielplatz', 'Zeltplatz', + 'Campingplatz', 'Badeplatz', 'FreizeitErholung', + 'Kleintierhaltung', 'Festplatz', 'SpezGruenflaeche', + 'StrassenbegleitGruen', 'BoeschungsFlaeche', 'FeldWaldWiese', + 'Uferschutzstreifen', 'Abschirmgruen', + 'UmweltbildungsparkSchaugatter', 'RuhenderVerkehr', 'Friedhof', + 'Sonstiges', 'Gaertnerei', name='xp_zweckbestimmunggruen'), + nullable=True), + sa.Column('nutzungsform', sa.Enum('Privat', 'Oeffentlich', name='xp_nutzungsform'), nullable=True), + sa.Column('zugunstenVon', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_landwirtschaft', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('zweckbestimmung', sa.Enum('LandwirtschaftAllgemein', 'Ackerbau', 'WiesenWeidewirtschaft', + 'GartenbaulicheErzeugung', 'Obstbau', 'Weinbau', 'Imkerei', + 'Binnenfischerei', 'Sonstiges', + name='xp_zweckbestimmunglandwirtschaft'), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_pflanzung', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('massnahme', sa.Enum('BindungErhaltung', 'Anpflanzung', name='xp_abemassnahmentypen'), + nullable=True), + sa.Column('gegenstand', + sa.Enum('Baeume', 'Kopfbaeume', 'Baumreihe', 'Straeucher', 'BaeumeUndStraeucher', 'Hecke', + 'Knick', 'SonstBepflanzung', 'Gewaesser', 'Fassadenbegruenung', 'Dachbegruenung', + name='xp_anpflanzungbindungerhaltungsgegenstand'), nullable=True), + sa.Column('kronendurchmesser', sa.Float(), nullable=True), + sa.Column('pflanztiefe', sa.Float(), nullable=True), + sa.Column('istAusgleich', sa.Boolean(), nullable=True), + sa.Column('baumArt', sa.String(), nullable=True), + sa.Column('mindesthoehe', sa.Float(), nullable=True), + sa.Column('anzahl', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_spiel_sportanlage', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('MaxZahlWohnungen', sa.Integer(), nullable=True), + sa.Column('MinGRWohneinheit', sa.Float(), nullable=True), + sa.Column('Fmin', sa.Float(), nullable=True), + sa.Column('Fmax', sa.Float(), nullable=True), + sa.Column('Bmin', sa.Float(), nullable=True), + sa.Column('Bmax', sa.Float(), nullable=True), + sa.Column('Tmin', sa.Float(), nullable=True), + sa.Column('Tmax', sa.Float(), nullable=True), + sa.Column('GFZmin', sa.Float(), nullable=True), + sa.Column('GFZmax', sa.Float(), nullable=True), + sa.Column('GFZ', sa.Float(), nullable=True), + sa.Column('GFZ_Ausn', sa.Float(), nullable=True), + sa.Column('GFmin', sa.Float(), nullable=True), + sa.Column('GFmax', sa.Float(), nullable=True), + sa.Column('GF', sa.Float(), nullable=True), + sa.Column('GF_Ausn', sa.Float(), nullable=True), + sa.Column('BMZ', sa.Float(), nullable=True), + sa.Column('BMZ_Ausn', sa.Float(), nullable=True), + sa.Column('BM', sa.Float(), nullable=True), + sa.Column('BM_Ausn', sa.Float(), nullable=True), + sa.Column('GRZmin', sa.Float(), nullable=True), + sa.Column('GRZmax', sa.Float(), nullable=True), + sa.Column('GRZ', sa.Float(), nullable=True), + sa.Column('GRZ_Ausn', sa.Float(), nullable=True), + sa.Column('GRmin', sa.Float(), nullable=True), + sa.Column('GRmax', sa.Float(), nullable=True), + sa.Column('GR', sa.Float(), nullable=True), + sa.Column('GR_Ausn', sa.Float(), nullable=True), + sa.Column('Zmin', sa.Integer(), nullable=True), + sa.Column('Zmax', sa.Integer(), nullable=True), + sa.Column('Zzwingend', sa.Integer(), nullable=True), + sa.Column('Z', sa.Integer(), nullable=True), + sa.Column('Z_Ausn', sa.Integer(), nullable=True), + sa.Column('Z_Staffel', sa.Integer(), nullable=True), + sa.Column('Z_Dach', sa.Integer(), nullable=True), + sa.Column('ZUmin', sa.Integer(), nullable=True), + sa.Column('ZUmax', sa.Integer(), nullable=True), + sa.Column('ZUzwingend', sa.Integer(), nullable=True), + sa.Column('ZU', sa.Integer(), nullable=True), + sa.Column('ZU_Ausn', sa.Integer(), nullable=True), + sa.Column('zweckbestimmung', sa.Enum('Sportanlage', 'Spielanlage', 'SpielSportanlage', 'Sonstiges', + name='xp_zweckbestimmungspielsportanlage'), nullable=True), + sa.Column('zugunstenVon', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_strassenbegrenzung', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('bautiefe', sa.Float(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_strassenverkehr', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('MaxZahlWohnungen', sa.Integer(), nullable=True), + sa.Column('MinGRWohneinheit', sa.Float(), nullable=True), + sa.Column('Fmin', sa.Float(), nullable=True), + sa.Column('Fmax', sa.Float(), nullable=True), + sa.Column('Bmin', sa.Float(), nullable=True), + sa.Column('Bmax', sa.Float(), nullable=True), + sa.Column('Tmin', sa.Float(), nullable=True), + sa.Column('Tmax', sa.Float(), nullable=True), + sa.Column('GFZmin', sa.Float(), nullable=True), + sa.Column('GFZmax', sa.Float(), nullable=True), + sa.Column('GFZ', sa.Float(), nullable=True), + sa.Column('GFZ_Ausn', sa.Float(), nullable=True), + sa.Column('GFmin', sa.Float(), nullable=True), + sa.Column('GFmax', sa.Float(), nullable=True), + sa.Column('GF', sa.Float(), nullable=True), + sa.Column('GF_Ausn', sa.Float(), nullable=True), + sa.Column('BMZ', sa.Float(), nullable=True), + sa.Column('BMZ_Ausn', sa.Float(), nullable=True), + sa.Column('BM', sa.Float(), nullable=True), + sa.Column('BM_Ausn', sa.Float(), nullable=True), + sa.Column('GRZmin', sa.Float(), nullable=True), + sa.Column('GRZmax', sa.Float(), nullable=True), + sa.Column('GRZ', sa.Float(), nullable=True), + sa.Column('GRZ_Ausn', sa.Float(), nullable=True), + sa.Column('GRmin', sa.Float(), nullable=True), + sa.Column('GRmax', sa.Float(), nullable=True), + sa.Column('GR', sa.Float(), nullable=True), + sa.Column('GR_Ausn', sa.Float(), nullable=True), + sa.Column('Zmin', sa.Integer(), nullable=True), + sa.Column('Zmax', sa.Integer(), nullable=True), + sa.Column('Zzwingend', sa.Integer(), nullable=True), + sa.Column('Z', sa.Integer(), nullable=True), + sa.Column('Z_Ausn', sa.Integer(), nullable=True), + sa.Column('Z_Staffel', sa.Integer(), nullable=True), + sa.Column('Z_Dach', sa.Integer(), nullable=True), + sa.Column('ZUmin', sa.Integer(), nullable=True), + sa.Column('ZUmax', sa.Integer(), nullable=True), + sa.Column('ZUzwingend', sa.Integer(), nullable=True), + sa.Column('ZU', sa.Integer(), nullable=True), + sa.Column('ZU_Ausn', sa.Integer(), nullable=True), + sa.Column('nutzungsform', sa.Enum('Privat', 'Oeffentlich', name='xp_nutzungsform'), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_verkehr_besonders', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('MaxZahlWohnungen', sa.Integer(), nullable=True), + sa.Column('MinGRWohneinheit', sa.Float(), nullable=True), + sa.Column('Fmin', sa.Float(), nullable=True), + sa.Column('Fmax', sa.Float(), nullable=True), + sa.Column('Bmin', sa.Float(), nullable=True), + sa.Column('Bmax', sa.Float(), nullable=True), + sa.Column('Tmin', sa.Float(), nullable=True), + sa.Column('Tmax', sa.Float(), nullable=True), + sa.Column('GFZmin', sa.Float(), nullable=True), + sa.Column('GFZmax', sa.Float(), nullable=True), + sa.Column('GFZ', sa.Float(), nullable=True), + sa.Column('GFZ_Ausn', sa.Float(), nullable=True), + sa.Column('GFmin', sa.Float(), nullable=True), + sa.Column('GFmax', sa.Float(), nullable=True), + sa.Column('GF', sa.Float(), nullable=True), + sa.Column('GF_Ausn', sa.Float(), nullable=True), + sa.Column('BMZ', sa.Float(), nullable=True), + sa.Column('BMZ_Ausn', sa.Float(), nullable=True), + sa.Column('BM', sa.Float(), nullable=True), + sa.Column('BM_Ausn', sa.Float(), nullable=True), + sa.Column('GRZmin', sa.Float(), nullable=True), + sa.Column('GRZmax', sa.Float(), nullable=True), + sa.Column('GRZ', sa.Float(), nullable=True), + sa.Column('GRZ_Ausn', sa.Float(), nullable=True), + sa.Column('GRmin', sa.Float(), nullable=True), + sa.Column('GRmax', sa.Float(), nullable=True), + sa.Column('GR', sa.Float(), nullable=True), + sa.Column('GR_Ausn', sa.Float(), nullable=True), + sa.Column('Zmin', sa.Integer(), nullable=True), + sa.Column('Zmax', sa.Integer(), nullable=True), + sa.Column('Zzwingend', sa.Integer(), nullable=True), + sa.Column('Z', sa.Integer(), nullable=True), + sa.Column('Z_Ausn', sa.Integer(), nullable=True), + sa.Column('Z_Staffel', sa.Integer(), nullable=True), + sa.Column('Z_Dach', sa.Integer(), nullable=True), + sa.Column('ZUmin', sa.Integer(), nullable=True), + sa.Column('ZUmax', sa.Integer(), nullable=True), + sa.Column('ZUzwingend', sa.Integer(), nullable=True), + sa.Column('ZU', sa.Integer(), nullable=True), + sa.Column('ZU_Ausn', sa.Integer(), nullable=True), + sa.Column('zweckbestimmung', + sa.Enum('Parkierungsflaeche', 'Fussgaengerbereich', 'VerkehrsberuhigterBereich', + 'RadGehweg', 'Radweg', 'Gehweg', 'Wanderweg', 'ReitKutschweg', 'Wirtschaftsweg', + 'FahrradAbstellplatz', 'UeberfuehrenderVerkehrsweg', 'UnterfuehrenderVerkehrsweg', + 'P_RAnlage', 'Platz', 'Anschlussflaeche', 'LandwirtschaftlicherVerkehr', + 'Verkehrsgruen', 'Rastanlage', 'Busbahnhof', 'CarSharing', 'BikeSharing', + 'B_RAnlage', 'Parkhaus', 'Mischverkehrsflaeche', 'Ladestation', 'Sonstiges', + name='bp_zweckbestimmungstrassenverkehr'), nullable=True), + sa.Column('nutzungsform', sa.Enum('Privat', 'Oeffentlich', name='xp_nutzungsform'), nullable=True), + sa.Column('zugunstenVon', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_versorgung', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('MaxZahlWohnungen', sa.Integer(), nullable=True), + sa.Column('MinGRWohneinheit', sa.Float(), nullable=True), + sa.Column('Fmin', sa.Float(), nullable=True), + sa.Column('Fmax', sa.Float(), nullable=True), + sa.Column('Bmin', sa.Float(), nullable=True), + sa.Column('Bmax', sa.Float(), nullable=True), + sa.Column('Tmin', sa.Float(), nullable=True), + sa.Column('Tmax', sa.Float(), nullable=True), + sa.Column('GFZmin', sa.Float(), nullable=True), + sa.Column('GFZmax', sa.Float(), nullable=True), + sa.Column('GFZ', sa.Float(), nullable=True), + sa.Column('GFZ_Ausn', sa.Float(), nullable=True), + sa.Column('GFmin', sa.Float(), nullable=True), + sa.Column('GFmax', sa.Float(), nullable=True), + sa.Column('GF', sa.Float(), nullable=True), + sa.Column('GF_Ausn', sa.Float(), nullable=True), + sa.Column('BMZ', sa.Float(), nullable=True), + sa.Column('BMZ_Ausn', sa.Float(), nullable=True), + sa.Column('BM', sa.Float(), nullable=True), + sa.Column('BM_Ausn', sa.Float(), nullable=True), + sa.Column('GRZmin', sa.Float(), nullable=True), + sa.Column('GRZmax', sa.Float(), nullable=True), + sa.Column('GRZ', sa.Float(), nullable=True), + sa.Column('GRZ_Ausn', sa.Float(), nullable=True), + sa.Column('GRmin', sa.Float(), nullable=True), + sa.Column('GRmax', sa.Float(), nullable=True), + sa.Column('GR', sa.Float(), nullable=True), + sa.Column('GR_Ausn', sa.Float(), nullable=True), + sa.Column('Zmin', sa.Integer(), nullable=True), + sa.Column('Zmax', sa.Integer(), nullable=True), + sa.Column('Zzwingend', sa.Integer(), nullable=True), + sa.Column('Z', sa.Integer(), nullable=True), + sa.Column('Z_Ausn', sa.Integer(), nullable=True), + sa.Column('Z_Staffel', sa.Integer(), nullable=True), + sa.Column('Z_Dach', sa.Integer(), nullable=True), + sa.Column('ZUmin', sa.Integer(), nullable=True), + sa.Column('ZUmax', sa.Integer(), nullable=True), + sa.Column('ZUzwingend', sa.Integer(), nullable=True), + sa.Column('ZU', sa.Integer(), nullable=True), + sa.Column('ZU_Ausn', sa.Integer(), nullable=True), + sa.Column('zweckbestimmung', + sa.Enum('Elektrizitaet', 'Hochspannungsleitung', 'TrafostationUmspannwerk', + 'Solarkraftwerk', 'Windkraftwerk', 'Geothermiekraftwerk', 'Elektrizitaetswerk', + 'Wasserkraftwerk', 'BiomasseKraftwerk', 'Kabelleitung', 'Niederspannungsleitung', + 'Leitungsmast', 'Kernkraftwerk', 'Kohlekraftwerk', 'Gaskraftwerk', 'Gas', + 'Ferngasleitung', 'Gaswerk', 'Gasbehaelter', 'Gasdruckregler', 'Gasstation', + 'Gasleitung', 'Erdoel', 'Erdoelleitung', 'Bohrstelle', 'Erdoelpumpstation', + 'Oeltank', 'Waermeversorgung', 'Blockheizkraftwerk', 'Fernwaermeleitung', + 'Fernheizwerk', 'Wasser', 'Wasserwerk', 'Wasserleitung', 'Wasserspeicher', + 'Brunnen', 'Pumpwerk', 'Quelle', 'Abwasser', 'Abwasserleitung', + 'Abwasserrueckhaltebecken', 'Abwasserpumpwerk', 'Klaeranlage', + 'AnlageKlaerschlamm', 'SonstigeAbwasserBehandlungsanlage', + 'SalzOderSoleleitungen', 'Regenwasser', 'RegenwasserRueckhaltebecken', + 'Niederschlagswasserleitung', 'Abfallentsorgung', 'Muellumladestation', + 'Muellbeseitigungsanlage', 'Muellsortieranlage', 'Recyclinghof', 'Ablagerung', + 'Erdaushubdeponie', 'Bauschuttdeponie', 'Hausmuelldeponie', 'Sondermuelldeponie', + 'StillgelegteDeponie', 'RekultivierteDeponie', 'Telekommunikation', + 'Fernmeldeanlage', 'Mobilfunkanlage', 'Fernmeldekabel', 'ErneuerbareEnergien', + 'KraftWaermeKopplung', 'Sonstiges', 'Produktenleitung', + name='xp_zweckbestimmungverentsorgung'), nullable=True), + sa.Column('textlicheErgaenzung', sa.String(), nullable=True), + sa.Column('zugunstenVon', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_wald', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('zweckbestimmung', + sa.Enum('Naturwald', 'Waldschutzgebiet', 'Nutzwald', 'Erholungswald', 'Schutzwald', + 'Bodenschutzwald', 'Biotopschutzwald', 'NaturnaherWald', + 'SchutzwaldSchaedlicheUmwelteinwirkungen', 'Schonwald', 'Bannwald', + 'FlaecheForstwirtschaft', 'ImmissionsgeschaedigterWald', 'Sonstiges', + name='xp_zweckbestimmungwald'), nullable=True), + sa.Column('eigentumsart', + sa.Enum('OeffentlicherWald', 'Staatswald', 'Koerperschaftswald', 'Kommunalwald', + 'Stiftungswald', 'Privatwald', 'Gemeinschaftswald', 'Genossenschaftswald', + 'Kirchenwald', 'Sonstiges', name='xp_eigentumsartwald'), nullable=True), + sa.Column('betreten', + sa.Enum('KeineZusaetzlicheBetretung', 'Radfahren', 'Reiten', 'Fahren', 'Hundesport', + 'Sonstiges', name='xp_waldbetretungtyp'), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.add_column('bp_dachgestaltung', sa.Column('besondere_nutzung_id', postgresql.UUID(as_uuid=True), nullable=True)) + op.add_column('bp_dachgestaltung', sa.Column('gemeinbedarf_id', postgresql.UUID(as_uuid=True), nullable=True)) + op.create_foreign_key('fk_dachgestaltung_gemeinbedarf', 'bp_dachgestaltung', 'bp_gemeinbedarf', ['gemeinbedarf_id'], + ['id'], ondelete='CASCADE') + op.create_foreign_key('fk_dachgestaltung_besondere_nutzung', 'bp_dachgestaltung', 'bp_besondere_nutzung', + ['besondere_nutzung_id'], ['id'], ondelete='CASCADE') + + +def downgrade(): + op.drop_constraint('fk_dachgestaltung_gemeinbedarf', 'bp_dachgestaltung', type_='foreignkey') + op.drop_constraint('fk_dachgestaltung_besondere_nutzung', 'bp_dachgestaltung', type_='foreignkey') + op.drop_column('bp_dachgestaltung', 'gemeinbedarf_id') + op.drop_column('bp_dachgestaltung', 'besondere_nutzung_id') + + op.execute("DROP TYPE xp_waldbetretungtyp CASCADE;") + op.execute("DROP TYPE xp_eigentumsartwald CASCADE;") + op.execute("DROP TYPE xp_zweckbestimmungwald CASCADE;") + op.execute("DROP TYPE xp_zweckbestimmungverentsorgung CASCADE;") + op.execute("DROP TYPE bp_zweckbestimmungstrassenverkehr CASCADE;") + op.execute("DROP TYPE xp_zweckbestimmungspielsportanlage CASCADE;") + op.execute("DROP TYPE xp_anpflanzungbindungerhaltungsgegenstand CASCADE;") + op.execute("DROP TYPE xp_zweckbestimmunglandwirtschaft CASCADE;") + op.execute("DROP TYPE xp_zweckbestimmunggruen CASCADE;") + op.execute("DROP TYPE xp_zweckbestimmunggewaesser CASCADE;") + op.execute("DROP TYPE xp_zweckbestimmunggemeinbedarf CASCADE;") + op.execute("DROP TYPE xp_nutzungsform CASCADE;") + op.execute("DROP TYPE xp_abemassnahmentypen CASCADE;") + + op.execute("DELETE FROM xp_objekt CASCADE WHERE type in ('bp_versorgung', 'bp_wald', 'bp_verkehr_besonders', " + "'bp_strassenverkehr', 'bp_strassenbegrenzung', 'bp_spiel_sportanlage', 'bp_pflanzung', " + "'bp_landwirtschaft', 'bp_gruenflaeche', 'bp_gewaesser', 'bp_gemeinbedarf', 'bp_besondere_nutzung', " + "'bp_baulinie');") + + op.drop_table('bp_wald') + op.drop_table('bp_versorgung') + op.drop_table('bp_verkehr_besonders') + op.drop_table('bp_strassenverkehr') + op.drop_table('bp_strassenbegrenzung') + op.drop_table('bp_spiel_sportanlage') + op.drop_table('bp_pflanzung') + op.drop_table('bp_landwirtschaft') + op.drop_table('bp_gruenflaeche') + op.drop_table('bp_gewaesser') + op.drop_table('bp_gemeinbedarf') + op.drop_table('bp_besondere_nutzung') + op.drop_table('bp_baulinie') diff --git a/alembic/versions/54455ce1e9f6_v1_5_0.py b/alembic/versions/54455ce1e9f6_v1_5_0.py new file mode 100644 index 0000000..d59b4ae --- /dev/null +++ b/alembic/versions/54455ce1e9f6_v1_5_0.py @@ -0,0 +1,248 @@ +"""v1.5.0 + +Revision ID: 54455ce1e9f6 +Revises: 2cee32cfc646 +Create Date: 2022-04-20 10:30:40.643937 + +""" +import os +import sys +from geoalchemy2 import Geometry +from alembic import op, context +import sqlalchemy as sa + +from sqlalchemy.dialects import postgresql + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + +# revision identifiers, used by Alembic. +revision = '54455ce1e9f6' +down_revision = '2cee32cfc646' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table('bp_schutzflaeche', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('ziel', + sa.Enum('SchutzPflege', 'Entwicklung', 'Anlage', 'SchutzPflegeEntwicklung', 'Sonstiges', + name='xp_speziele'), nullable=True), + sa.Column('sonstZiel', sa.String(), nullable=True), + sa.Column('istAusgleich', sa.Boolean(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('xp_spe_daten', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('klassifizMassnahme', + sa.Enum('ArtentreicherGehoelzbestand', 'NaturnaherWald', 'ExtensivesGruenland', + 'Feuchtgruenland', 'Obstwiese', 'NaturnaherUferbereich', 'Roehrichtzone', + 'Ackerrandstreifen', 'Ackerbrache', 'Gruenlandbrache', 'Sukzessionsflaeche', + 'Hochstaudenflur', 'Trockenrasen', 'Heide', 'Sonstiges', + name='xp_spemassnahmentypen'), nullable=True), + sa.Column('massnahmeText', sa.String(), nullable=True), + sa.Column('massnahmeKuerzel', sa.String(), nullable=True), + sa.Column('bp_schutzflaeche_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.ForeignKeyConstraint(['bp_schutzflaeche_id'], ['bp_schutzflaeche.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_wegerecht', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('typ', + sa.ARRAY(sa.Enum('Gehrecht', 'Fahrrecht', 'Radfahrrecht', 'Leitungsrecht', 'Sonstiges', + name='bp_wegerechttypen')), nullable=True), + sa.Column('zugunstenVon', sa.String(), nullable=True), + sa.Column('thema', sa.String(), nullable=True), + sa.Column('breite', sa.Float(), nullable=True), + sa.Column('istSchmal', sa.Boolean(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + + bundesland_enum = postgresql.ENUM('BB', 'BE', 'BW', 'BY', 'HB', 'HE', 'HH', 'MV', 'NI', 'NW', 'RP', 'SH', 'SL', + 'SN', 'ST', 'TH', 'Bund', name='xp_bundeslaender') + + # RP + op.create_table('rp_plan', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('bundesland', bundesland_enum, nullable=True), + sa.Column('planArt', sa.Enum('Regionalplan', 'SachlicherTeilplanRegionalebene', + 'SachlicherTeilplanLandesebene', 'Braunkohlenplan', + 'LandesweiterRaumordnungsplan', 'StandortkonzeptBund', 'AWZPlan', + 'RaeumlicherTeilplan', 'Sonstiges', name='rp_art'), nullable=False), + sa.Column('planungsregion', sa.Integer(), nullable=True), + sa.Column('teilabschnitt', sa.Integer(), nullable=True), + sa.Column('rechtsstand', + sa.Enum('Aufstellungsbeschluss', 'Entwurf', 'EntwurfGenehmigt', 'EntwurfGeaendert', + 'EntwurfAufgegeben', 'EntwurfRuht', 'Plan', 'Inkraftgetreten', + 'AllgemeinePlanungsabsicht', 'AusserKraft', 'PlanUngueltig', + name='rp_rechtsstand'), nullable=True), + sa.Column('aufstellungsbeschlussDatum', sa.Date(), nullable=True), + sa.Column('auslegungsStartDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('auslegungsEndDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('traegerbeteiligungsStartDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('traegerbeteiligungsEndDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('aenderungenBisDatum', sa.Date(), nullable=True), + sa.Column('entwurfsbeschlussDatum', sa.Date(), nullable=True), + sa.Column('planbeschlussDatum', sa.Date(), nullable=True), + sa.Column('datumDesInkrafttretens', sa.Date(), nullable=True), + sa.Column('verfahren', + sa.Enum('Aenderung', 'Teilfortschreibung', 'Neuaufstellung', 'Gesamtfortschreibung', + 'Aktualisierung', 'Neubekanntmachung', name='rp_verfahren'), nullable=True), + sa.Column('amtlicherSchluessel', sa.String(), nullable=True), + sa.Column('genehmigungsbehoerde', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['xp_plan.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('rp_bereich', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('versionBROG', sa.Date(), nullable=True), + sa.Column('versionBROGText', sa.String(), nullable=True), + sa.Column('versionLPLG', sa.Date(), nullable=True), + sa.Column('versionLPLGText', sa.String(), nullable=True), + sa.Column('geltungsmassstab', sa.Integer(), nullable=True), + sa.Column('gehoertZuPlan_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.ForeignKeyConstraint(['gehoertZuPlan_id'], ['rp_plan.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['id'], ['xp_bereich.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + # LP + op.create_table('lp_plan', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('bundesland', bundesland_enum, nullable=True), + sa.Column('rechtlicheAussenwirkung', sa.Boolean(), nullable=True), + sa.Column('planArt', sa.ARRAY( + sa.Enum('Landschaftsprogramm', 'Landschaftsrahmenplan', 'Landschaftsplan', 'Gruenordnungsplan', + 'Sonstiges', name='lp_planart')), nullable=False), + sa.Column('planungstraegerGKZ', sa.String(), nullable=True), + sa.Column('planungstraeger', sa.String(), nullable=True), + sa.Column('rechtsstand', + sa.Enum('Aufstellungsbeschluss', 'Entwurf', 'Plan', 'Wirksamkeit', 'Untergegangen', + name='lp_rechtsstand'), nullable=True), + sa.Column('aufstellungsbeschlussDatum', sa.Date(), nullable=True), + sa.Column('auslegungsDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('tOeBbeteiligungsDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('oeffentlichkeitsbeteiligungDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('aenderungenBisDatum', sa.Date(), nullable=True), + sa.Column('entwurfsbeschlussDatum', sa.Date(), nullable=True), + sa.Column('planbeschlussDatum', sa.Date(), nullable=True), + sa.Column('inkrafttretenDatum', sa.Date(), nullable=True), + sa.Column('sonstVerfahrensDatum', sa.Date(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['xp_plan.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('lp_bereich', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('gehoertZuPlan_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.ForeignKeyConstraint(['gehoertZuPlan_id'], ['lp_plan.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['id'], ['xp_bereich.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + + op.add_column('xp_externe_referenz', + sa.Column('bp_schutzflaeche_massnahme_id', postgresql.UUID(as_uuid=True), nullable=True)) + op.add_column('xp_externe_referenz', + sa.Column('bp_schutzflaeche_plan_id', postgresql.UUID(as_uuid=True), nullable=True)) + op.create_foreign_key('fk_schutzflaeche_plan_ref', 'xp_externe_referenz', 'bp_schutzflaeche', + ['bp_schutzflaeche_plan_id'], ['id'], ondelete='CASCADE') + op.create_foreign_key('fk_schutzflaeche_massnahme_ref', 'xp_externe_referenz', 'bp_schutzflaeche', + ['bp_schutzflaeche_massnahme_id'], ['id'], ondelete='CASCADE') + + op.add_column('xp_tpo', sa.Column('skalierung', sa.Float(), nullable=True, server_default='0.5')) + op.add_column('xp_objekt', sa.Column('skalierung', sa.Float(), nullable=True, server_default='0.5')) + op.add_column('xp_objekt', sa.Column('drehwinkel', sa.Integer(), nullable=True, server_default='0')) + + # op.drop_constraint('xp_plan_gemeinde_plan_id_fkey', 'xp_plan_gemeinde', type_='foreignkey') + # op.create_foreign_key('xp_plan_gemeinde_plan_id_fkey', 'xp_plan_gemeinde', 'xp_plan', ['plan_id'], ['id'], ondelete='CASCADE') + + # refactor plangeber relations + op.add_column('bp_plan', sa.Column('plangeber_id', postgresql.UUID(as_uuid=True), nullable=True)) + op.create_foreign_key('fk_plangeber_bp_plan', 'bp_plan', 'xp_plangeber', ['plangeber_id'], ['id']) + + op.add_column('fp_plan', sa.Column('plangeber_id', postgresql.UUID(as_uuid=True), nullable=True)) + op.create_foreign_key('fk_plangeber_fp_plan', 'fp_plan', 'xp_plangeber', ['plangeber_id'], ['id']) + + op.execute("UPDATE bp_plan bp SET plangeber_id = xp.plangeber_id FROM xp_plan xp WHERE xp.id = bp.id;") + op.execute("UPDATE fp_plan fp SET plangeber_id = xp.plangeber_id FROM xp_plan xp WHERE xp.id = fp.id;") + + op.drop_constraint('xp_plan_plangeber_id_fkey', 'xp_plan', type_='foreignkey') + op.drop_column('xp_plan', 'plangeber_id') + + # refactor gemeinde relations + op.add_column('xp_plan_gemeinde', sa.Column('bp_plan_id', postgresql.UUID(as_uuid=True), nullable=True)) + op.add_column('xp_plan_gemeinde', sa.Column('fp_plan_id', postgresql.UUID(as_uuid=True), nullable=True)) + op.drop_constraint('xp_plan_gemeinde_plan_id_fkey', 'xp_plan_gemeinde', type_='foreignkey') + + op.create_foreign_key('xp_plan_gemeinde_bp_plan_id_fkey', 'xp_plan_gemeinde', 'bp_plan', ['bp_plan_id'], ['id'], + ondelete='CASCADE') + op.create_foreign_key('xp_plan_gemeinde_fp_plan_id_fkey', 'xp_plan_gemeinde', 'fp_plan', ['fp_plan_id'], ['id'], + ondelete='CASCADE') + + op.execute("UPDATE xp_plan_gemeinde g SET bp_plan_id = xp.id FROM xp_plan xp WHERE xp.id = g.plan_id AND xp.type = 'bp_plan';") + op.execute("UPDATE xp_plan_gemeinde g SET fp_plan_id = xp.id FROM xp_plan xp WHERE xp.id = g.plan_id AND xp.type = 'fp_plan';") + + op.drop_column('xp_plan_gemeinde', 'plan_id') + + +def downgrade(): + op.drop_column('xp_tpo', 'skalierung') + op.drop_column('xp_objekt', 'skalierung') + op.drop_column('xp_objekt', 'drehwinkel') + + op.drop_constraint('fk_schutzflaeche_plan_ref', 'xp_externe_referenz', type_='foreignkey') + op.drop_constraint('fk_schutzflaeche_massnahme_ref', 'xp_externe_referenz', type_='foreignkey') + op.drop_column('xp_externe_referenz', 'bp_schutzflaeche_plan_id') + op.drop_column('xp_externe_referenz', 'bp_schutzflaeche_massnahme_id') + + op.drop_table('xp_spe_daten') + op.drop_table('bp_schutzflaeche') + op.drop_table('bp_wegerecht') + + op.drop_table('rp_bereich') + op.drop_table('rp_plan') + op.drop_table('lp_bereich') + op.drop_table('lp_plan') + + op.execute("DELETE FROM xp_objekt CASCADE WHERE type in ('bp_schutzflaeche', 'bp_wegerecht');") + op.execute("DELETE FROM xp_bereich CASCADE WHERE type in ('rp_bereich', 'lp_bereich');") + op.execute("DELETE FROM xp_plan CASCADE WHERE type in ('rp_plan', 'lp_plan');") + + op.execute("DROP TYPE xp_speziele CASCADE;") + op.execute("DROP TYPE xp_spemassnahmentypen CASCADE;") + op.execute("DROP TYPE bp_wegerechttypen CASCADE;") + op.execute("DROP TYPE xp_bundeslaender CASCADE;") + op.execute("DROP TYPE rp_art CASCADE;") + op.execute("DROP TYPE rp_rechtsstand CASCADE;") + op.execute("DROP TYPE rp_verfahren CASCADE;") + op.execute("DROP TYPE lp_planart CASCADE;") + op.execute("DROP TYPE lp_rechtsstand CASCADE;") + + # revert: refactor plangeber relations + op.add_column('xp_plan', sa.Column('plangeber_id', postgresql.UUID(), autoincrement=False, nullable=True)) + op.create_foreign_key('xp_plan_plangeber_id_fkey', 'xp_plan', 'xp_plangeber', ['plangeber_id'], ['id']) + + op.execute("UPDATE xp_plan xp SET plangeber_id = bp.plangeber_id FROM bp_plan bp WHERE bp.id = xp.id") + op.execute("UPDATE xp_plan xp SET plangeber_id = fp.plangeber_id FROM fp_plan fp WHERE fp.id = xp.id") + + op.drop_constraint('fk_plangeber_fp_plan', 'fp_plan', type_='foreignkey') + op.drop_constraint('fk_plangeber_bp_plan', 'bp_plan', type_='foreignkey') + op.drop_column('bp_plan', 'plangeber_id') + op.drop_column('fp_plan', 'plangeber_id') + + # revert: refactor gemeinde relations + op.add_column('xp_plan_gemeinde', sa.Column('plan_id', postgresql.UUID(), autoincrement=False, nullable=True)) + + op.drop_constraint('xp_plan_gemeinde_bp_plan_id_fkey', 'xp_plan_gemeinde', type_='foreignkey') + op.drop_constraint('xp_plan_gemeinde_fp_plan_id_fkey', 'xp_plan_gemeinde', type_='foreignkey') + op.create_foreign_key('xp_plan_gemeinde_plan_id_fkey', 'xp_plan_gemeinde', 'xp_plan', ['plan_id'], ['id']) + + op.execute("UPDATE xp_plan_gemeinde g SET plan_id = xp.id FROM xp_plan xp WHERE xp.id = g.bp_plan_id;") + op.execute("UPDATE xp_plan_gemeinde g SET plan_id = xp.id FROM xp_plan xp WHERE xp.id = g.fp_plan_id;") + + op.drop_column('xp_plan_gemeinde', 'fp_plan_id') + op.drop_column('xp_plan_gemeinde', 'bp_plan_id') diff --git a/alembic/versions/812b33dce3d1_v1_8_0.py b/alembic/versions/812b33dce3d1_v1_8_0.py new file mode 100644 index 0000000..3feed1f --- /dev/null +++ b/alembic/versions/812b33dce3d1_v1_8_0.py @@ -0,0 +1,148 @@ +"""v1.8.0 + +Revision ID: 812b33dce3d1 +Revises: 1a015621a38f +Create Date: 2022-12-02 09:20:54.695508 + +""" +import os +import sys + +from alembic import op +import sqlalchemy as sa + +from geoalchemy2 import Geometry +from sqlalchemy.dialects import postgresql + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + +# revision identifiers, used by Alembic. +revision = '812b33dce3d1' +down_revision = '1a015621a38f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_geospatial_table('so_objekt', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('rechtscharakter', + sa.Enum('FestsetzungBPlan', 'DarstellungFPlan', 'InhaltLPlan', + 'NachrichtlicheUebernahme', 'Hinweis', 'Vermerk', 'Kennzeichnung', + 'Unbekannt', 'Sonstiges', name='so_rechtscharakter'), nullable=False), + sa.Column('position', + Geometry(spatial_index=False, from_text='ST_GeomFromEWKT', name='geometry'), + nullable=True), + sa.Column('flaechenschluss', sa.Boolean(), nullable=True), + sa.Column('flussrichtung', sa.Boolean(), nullable=True), + sa.Column('nordwinkel', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['xp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_geospatial_index('idx_so_objekt_position', 'so_objekt', ['position'], unique=False, + postgresql_using='gist', postgresql_ops={}) + op.create_table('so_denkmalschutz', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('artDerFestlegung', + sa.Enum('DenkmalschutzEnsemble', 'DenkmalschutzEinzelanlage', 'Grabungsschutzgebiet', + 'PufferzoneWeltkulturerbeEnger', 'PufferzoneWeltkulturerbeWeiter', + 'ArcheologischesDenkmal', 'Bodendenkmal', 'Sonstiges', + name='so_klassifiznachdenkmalschutzrecht'), nullable=True), + sa.Column('weltkulturerbe', sa.Boolean(), nullable=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('nummer', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['so_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('so_schienenverkehr', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('artDerFestlegung', + sa.Enum('Bahnanlage', 'DB_Bahnanlage', 'Personenbahnhof', 'Fernbahnhof', 'Gueterbahnhof', + 'Bahnlinie', 'Personenbahnlinie', 'Regionalbahn', 'Kleinbahn', 'Gueterbahnlinie', + 'WerksHafenbahn', 'Seilbahn', 'OEPNV', 'Strassenbahn', 'UBahn', 'SBahn', + 'OEPNV_Haltestelle', 'Sonstiges', name='so_klassifiznachschienenverkehrsrecht'), + nullable=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('nummer', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['so_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + + op.create_table('so_wasserschutz', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('artDerFestlegung', sa.Enum('Wasserschutzgebiet', 'QuellGrundwasserSchutzgebiet', + 'OberflaechengewaesserSchutzgebiet', + 'Heilquellenschutzgebiet', 'Sonstiges', + name='so_klassifizschutzgebietwasserrecht'), nullable=True), + sa.Column('zone', sa.Enum('Zone_1', 'Zone_2', 'Zone_3', 'Zone_3a', 'Zone_3b', 'Zone_4', + name='so_schutzzonenwasserrecht'), nullable=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('nummer', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['so_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + + op.execute( + "UPDATE bp_baugebiet SET sondernutzung=NULL WHERE sondernutzung='KeineSondernutzung'::xp_sondernutzungen;") + op.execute( + "ALTER TABLE bp_baugebiet ALTER sondernutzung DROP DEFAULT, ALTER sondernutzung type xp_sondernutzungen[] using NULLIF(ARRAY[sondernutzung], '{null}'), alter sondernutzung set default '{}';") + + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_Naturschutz_Landschaft', 'BP_Naturschutz_Landschaftsbild_Naturhaushalt');") + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_GruenFlaeche', 'BP_Landwirtschaft_Wald_und_Gruenflaechen');") + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_WaldFlaeche', 'BP_Landwirtschaft_Wald_und_Gruenflaechen');") + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_GemeinbedarfsFlaeche', 'BP_Gemeinbedarf_Spiel_und_Sportanlagen');") + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_Erhaltungssatzung_und_Denkmalschutz', 'SO_SonstigeGebiete');") + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_VerEntsorgung', 'BP_Ver_und_Entsorgung');") + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_SpielSportanlagenFlaeche', 'BP_Gemeinbedarf_Spiel_und_Sportanlagen');") + # ### end Alembic commands ### + + +def downgrade(): + op.execute( + "DELETE FROM xp_objekt CASCADE WHERE type in ('so_schienenverkehr', 'so_denkmalschutz', 'so_wasserschutz');") + + op.drop_table('so_schienenverkehr') + op.drop_table('so_denkmalschutz') + op.drop_table('so_wasserschutz') + op.drop_geospatial_index('idx_so_objekt_position', table_name='so_objekt', postgresql_using='gist', + column_name='position') + op.drop_geospatial_table('so_objekt') + + op.execute("DROP TYPE so_klassifiznachschienenverkehrsrecht CASCADE;") + op.execute("DROP TYPE so_klassifiznachdenkmalschutzrecht CASCADE;") + op.execute("DROP TYPE so_rechtscharakter CASCADE;") + op.execute("DROP TYPE so_klassifizschutzgebietwasserrecht CASCADE;") + op.execute("DROP TYPE so_schutzzonenwasserrecht CASCADE;") + + op.execute( + "ALTER TABLE bp_baugebiet ALTER sondernutzung DROP DEFAULT, ALTER sondernutzung type xp_sondernutzungen using sondernutzung[1], alter sondernutzung set default NULL;") + + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_Naturschutz_Landschaftsbild_Naturhaushalt', 'BP_Naturschutz_Landschaft');") + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_Landwirtschaft_Wald_und_Gruenflaechen', 'BP_WaldFlaeche') " + "WHERE position('Erholungswald' IN symbol_path) > 0;") + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_Landwirtschaft_Wald_und_Gruenflaechen', 'BP_GruenFlaeche');") + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_Gemeinbedarf_Spiel_und_Sportanlagen', 'BP_SpielSportanlagenFlaeche') " + "WHERE position('Anlage_Spielanlage' IN symbol_path) > 0 OR position('Anlage_Sportanlage' IN symbol_path) > 0;") + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_Gemeinbedarf_Spiel_und_Sportanlagen', 'BP_GemeinbedarfsFlaeche');") + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'SO_SonstigeGebiete', 'BP_Erhaltungssatzung_und_Denkmalschutz');") + op.execute( + "UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_Ver_und_Entsorgung', 'BP_VerEntsorgung');") + # ### end Alembic commands ### diff --git a/alembic/versions/873e18b73036_v1_6_0.py b/alembic/versions/873e18b73036_v1_6_0.py new file mode 100644 index 0000000..91c8b84 --- /dev/null +++ b/alembic/versions/873e18b73036_v1_6_0.py @@ -0,0 +1,135 @@ +"""v1.6.0 + +Revision ID: 873e18b73036 +Revises: 54455ce1e9f6 +Create Date: 2022-06-29 08:43:45.136458 + +""" +import os +import sys + +from geoalchemy2 import Geometry +from alembic import op +import sqlalchemy as sa + +from sqlalchemy.dialects import postgresql + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + +# revision identifiers, used by Alembic. +revision = '873e18b73036' +down_revision = '54455ce1e9f6' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('xp_nutzungsschablone', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('position', Geometry(geometry_type='POINT', from_text='ST_GeomFromEWKT', + name='geometry'), nullable=True), + sa.Column('drehwinkel', sa.Integer(), nullable=True), + sa.Column('skalierung', sa.Float(), nullable=True), + sa.Column('spaltenAnz', sa.Integer(), nullable=False), + sa.Column('zeilenAnz', sa.Integer(), nullable=False), + sa.Column('hidden', sa.Boolean(), nullable=True), + sa.Column('data_attributes', sa.ARRAY( + sa.Enum('ArtDerBaulNutzung', 'ZahlVollgeschosse', 'GRZ', 'GFZ', 'BebauungsArt', 'Bauweise', + 'Dachneigung', 'Dachform', name='buildingtemplatecelldatatype')), nullable=True), + sa.ForeignKeyConstraint(['id'], ['xp_po.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + + op.add_column('xp_po', sa.Column('dientZurDarstellungVon_id', postgresql.UUID(as_uuid=True), nullable=True)) + op.create_foreign_key('fk_po_xp_object', 'xp_po', 'xp_objekt', ['dientZurDarstellungVon_id'], ['id'], ondelete='CASCADE') + + # update data: add `XP_Nutzungsschablone` to all `BP_Baugebietsteilfläche` + + op.execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') + + op.execute(f'INSERT INTO xp_po(id, type, "dientZurDarstellungVon_id") ' + f"SELECT uuid_generate_v4(), 'xp_nutzungsschablone', bp_baugebiet.id FROM bp_baugebiet") + + op.execute('INSERT INTO xp_nutzungsschablone(id, drehwinkel, skalierung, "spaltenAnz", "zeilenAnz", hidden) ' + f"SELECT xp_po.id, 0, 0.5, 2, 3, TRUE FROM xp_po WHERE xp_po.type = 'xp_nutzungsschablone'") + + # add the civil connector tables + op.execute(''' + create table civil_gemeinde + ( + id uuid not null + primary key, + ags varchar(8) not null, + rs varchar(12), + "gemeindeName" varchar not null, + "ortsteilName" varchar + ); + + create table civil_plan + ( + id uuid not null + primary key, + art bp_planart default 'BPlan'::bp_planart not null, + gemeinde_id uuid + constraint plan_gemeinde_fkey + references civil_gemeinde, + name varchar(255) not null + ); + + create table civil_area + ( + id uuid not null + constraint bp_area_pkey + primary key, + layer varchar(255) not null, + rechtscharakter bp_rechtscharakter default 'Unbekannt'::bp_rechtscharakter, + geom geometry(MultiPolygon) not null, + plan_id uuid + constraint area_plan_fkey + references civil_plan + ); + + create table civil_point + ( + id uuid not null + constraint bp_point_pkey + primary key, + layer varchar(255), + rechtscharakter bp_rechtscharakter default 'Unbekannt'::bp_rechtscharakter, + geom geometry(Point), + plan_id uuid + constraint point_plan_fkey + references civil_plan + ); + + create table civil_line + ( + id uuid not null + constraint bp_line_pkey + primary key, + layer varchar(255), + rechtscharakter bp_rechtscharakter default 'Unbekannt'::bp_rechtscharakter, + geom geometry(MultiLineString), + plan_id uuid + constraint line_plan_fkey + references civil_plan + ); + ''') + + +def downgrade(): + op.drop_constraint('fk_po_xp_object', 'xp_po', type_='foreignkey') + op.drop_column('xp_po', 'dientZurDarstellungVon_id') + + op.drop_table('xp_nutzungsschablone') + op.execute("DELETE FROM xp_po CASCADE WHERE type in ('xp_nutzungsschablone');") + + op.execute("DROP TYPE buildingtemplatecelldatatype CASCADE;") + + op.execute('DROP TABLE IF EXISTS civil_point, civil_line, civil_area, civil_plan, civil_gemeinde;') + # ### end Alembic commands ### diff --git a/alembic/versions/8cdbef1a55d6_v2_1_0.py b/alembic/versions/8cdbef1a55d6_v2_1_0.py new file mode 100644 index 0000000..829e665 --- /dev/null +++ b/alembic/versions/8cdbef1a55d6_v2_1_0.py @@ -0,0 +1,49 @@ +"""v2.1.0 + +Revision ID: 8cdbef1a55d6 +Revises: fb27f7a59e17 +Create Date: 2023-07-20 08:27:50.660157 + +""" +import os +import sys + +from alembic import op +import sqlalchemy as sa + +from geoalchemy2 import Geometry +from sqlalchemy.dialects import postgresql + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + + +# revision identifiers, used by Alembic. +revision = '8cdbef1a55d6' +down_revision = 'fb27f7a59e17' +branch_labels = None +depends_on = None + + +def upgrade(): + # CR-039 BP_Plan + op.execute(""" + ALTER TABLE bp_plan ADD COLUMN "versionBauNVO_id" uuid REFERENCES xp_gesetzliche_grundlage(id); + ALTER TABLE bp_plan ADD COLUMN "versionBauGB_id" uuid REFERENCES xp_gesetzliche_grundlage(id); + ALTER TABLE bp_plan ADD COLUMN "versionSonstRechtsgrundlage_id" uuid REFERENCES xp_gesetzliche_grundlage(id); + """) + + op.execute("ALTER TYPE xp_zweckbestimmunggemeinbedarf ADD VALUE 'SonstigeInfrastruktur'") + op.execute("ALTER TYPE xp_zweckbestimmunggemeinbedarf ADD VALUE 'SonstigeSicherheitOrdnung'") + + +def downgrade(): + op.execute(""" + ALTER TABLE bp_plan DROP COLUMN "versionBauNVO_id"; + ALTER TABLE bp_plan DROP COLUMN "versionBauGB_id"; + ALTER TABLE bp_plan DROP COLUMN "versionSonstRechtsgrundlage_id"; + """) + # ### end Alembic commands ### diff --git a/alembic/versions/b93ec8372df6_v1_10_0.py b/alembic/versions/b93ec8372df6_v1_10_0.py new file mode 100644 index 0000000..2078684 --- /dev/null +++ b/alembic/versions/b93ec8372df6_v1_10_0.py @@ -0,0 +1,47 @@ +"""v1.10.0 + +Revision ID: b93ec8372df6 +Revises: 0a6f00a1f663 +Create Date: 2023-02-23 10:33:01.286524 + +""" +import os +import sys + +from alembic import op +import sqlalchemy as sa + +from geoalchemy2 import Geometry +from sqlalchemy.dialects import postgresql + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + + +# revision identifiers, used by Alembic. +revision = 'b93ec8372df6' +down_revision = '0a6f00a1f663' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute('ALTER TABLE xp_plan ALTER COLUMN "raeumlicherGeltungsbereich" type geometry(Geometry);') + op.execute('ALTER TABLE xp_plan ADD CONSTRAINT check_geometry_type CHECK (st_dimension("raeumlicherGeltungsbereich") = 2);') + + op.execute('ALTER TABLE xp_bereich ALTER COLUMN "geltungsbereich" type geometry(Geometry);') + op.execute('ALTER TABLE xp_bereich ADD CONSTRAINT check_geometry_type CHECK (st_dimension("geltungsbereich") = 2);') + + +def downgrade(): + op.execute('DELETE FROM xp_plan WHERE st_dimension("raeumlicherGeltungsbereich") < 2 or ST_GeometryType("raeumlicherGeltungsbereich") = \'ST_CurvePolygon\';') + op.execute('ALTER TABLE xp_plan ALTER COLUMN "raeumlicherGeltungsbereich" type geometry(MultiPolygon);') + + op.execute('DELETE FROM xp_bereich WHERE st_dimension("geltungsbereich") < 2 or ST_GeometryType("geltungsbereich") = \'ST_CurvePolygon\';') + op.execute('ALTER TABLE xp_bereich ALTER COLUMN "geltungsbereich" type geometry(MultiPolygon);') + + op.drop_constraint('check_geometry_type', 'xp_plan') + op.drop_constraint('check_geometry_type', 'xp_bereich') diff --git a/alembic/versions/ce95b86bc010_v2_2_0.py b/alembic/versions/ce95b86bc010_v2_2_0.py new file mode 100644 index 0000000..fc00287 --- /dev/null +++ b/alembic/versions/ce95b86bc010_v2_2_0.py @@ -0,0 +1,216 @@ +"""v2.2.0 + +Revision ID: ce95b86bc010 +Revises: 8cdbef1a55d6 +Create Date: 2023-08-01 12:42:07.002746 + +""" +import os +import sys + +from alembic import op +import sqlalchemy as sa + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + +# revision identifiers, used by Alembic. +revision = 'ce95b86bc010' +down_revision = '8cdbef1a55d6' +branch_labels = None +depends_on = None + + +def upgrade(): + # region CODELISTS + op.execute(""" + CREATE TABLE codelist ( + id UUID NOT NULL, + name VARCHAR, + uri VARCHAR, + description VARCHAR, + + PRIMARY KEY (id) + ); + + CREATE TABLE codelist_values ( + id UUID NOT NULL, + value VARCHAR, + uri VARCHAR, + definition VARCHAR, + type VARCHAR, + + "codelist_id" UUID, + FOREIGN KEY("codelist_id") REFERENCES codelist (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + ALTER TABLE bp_plan ADD COLUMN "sonstPlanArt_id" uuid REFERENCES codelist_values(id); + ALTER TABLE bp_plan ADD COLUMN "status_id" uuid REFERENCES codelist_values(id); + ALTER TABLE bp_baugebiet ADD COLUMN "detaillierteArtDerBaulNutzung_id" uuid REFERENCES codelist_values(id); + ALTER TABLE bp_dachgestaltung ADD COLUMN "detaillierteDachform_id" uuid REFERENCES codelist_values(id); + + ALTER TABLE fp_plan ADD COLUMN "sonstPlanArt_id" uuid REFERENCES codelist_values(id); + ALTER TABLE fp_plan ADD COLUMN "status_id" uuid REFERENCES codelist_values(id); + ALTER TABLE fp_baugebiet ADD COLUMN "detaillierteArtDerBaulNutzung_id" uuid REFERENCES codelist_values(id); + + ALTER TABLE so_schienenverkehr ADD COLUMN "detailArtDerFestlegung_id" uuid REFERENCES codelist_values(id); + ALTER TABLE so_denkmalschutz ADD COLUMN "detailArtDerFestlegung_id" uuid REFERENCES codelist_values(id); + """ + ) + + op.execute(""" + CREATE TABLE assoc_detail_sondernutzung ( + "codelist_id" UUID, + "codelist_user_id" UUID, + + FOREIGN KEY("codelist_user_id") REFERENCES bp_komplexe_sondernutzung (id) ON DELETE CASCADE, + FOREIGN KEY("codelist_id") REFERENCES codelist_values (id), + PRIMARY KEY (codelist_id, codelist_user_id) + ); + + CREATE TABLE assoc_detail_zweckgruen ( + "codelist_id" UUID, + "codelist_user_id" UUID, + + FOREIGN KEY("codelist_user_id") REFERENCES bp_zweckbestimmung_gruen (id) ON DELETE CASCADE, + FOREIGN KEY("codelist_id") REFERENCES codelist_values (id), + PRIMARY KEY (codelist_id, codelist_user_id) + ); + """) + # endregion CODELISTS + + # region XP_Hoehenangabe + op.execute(""" + CREATE TYPE xp_arthoehenbezug AS ENUM ( + 'absolutNHN', + 'absolutNN', + 'absolutDHHN', + 'relativGelaendeoberkante', + 'relativGehwegOberkante', + 'relativBezugshoehe', + 'relativStrasse', + 'relativEFH' + ); + + CREATE TYPE xp_arthoehenbezugspunkt AS ENUM ( + 'TH', + 'FH', + 'OK', + 'LH', + 'SH', + 'EFH', + 'HBA', + 'UK', + 'GBH', + 'WH', + 'GOK' + ); + + CREATE TABLE xp_hoehenangabe ( + id UUID NOT NULL, + "abweichenderHoehenbezug" VARCHAR, + hoehenbezug xp_arthoehenbezug, + "abweichenderBezugspunkt" VARCHAR, + bezugspunkt xp_arthoehenbezugspunkt, + "hMin" FLOAT, + "hMax" FLOAT, + "hZwingend" FLOAT, + "h" FLOAT, + + "xp_objekt_id" UUID, + "dachgestaltung_id" UUID, + FOREIGN KEY("xp_objekt_id") REFERENCES xp_objekt (id) ON DELETE CASCADE, + FOREIGN KEY("dachgestaltung_id") REFERENCES bp_dachgestaltung (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + """) + # endregion XP_Hoehenangabe + + op.execute(""" + CREATE TYPE bp_abgrenzungentypen AS ENUM ( + 'Nutzungsartengrenze', + 'UnterschiedlicheHoehen', + 'SonstigeAbgrenzung' + ); + + CREATE TABLE bp_nutzungsgrenze ( + id UUID NOT NULL, + typ bp_abgrenzungentypen, + + PRIMARY KEY (id) + ); + """) + + op.execute(""" + CREATE TYPE bp_bereichohneeinausfahrttypen AS ENUM ( + 'KeineEinfahrt', + 'KeineAusfahrt', + 'KeineEinAusfahrt' + ); + + CREATE TABLE bp_keine_ein_ausfahrt ( + id UUID NOT NULL, + typ bp_bereichohneeinausfahrttypen, + + PRIMARY KEY (id) + ); + """) + + op.execute(""" + CREATE TYPE bp_einfahrttypen AS ENUM ( + 'Einfahrt', + 'Ausfahrt', + 'EinAusfahrt' + ); + + CREATE TABLE bp_einfahrtpunkt ( + id UUID NOT NULL, + typ bp_einfahrttypen, + + PRIMARY KEY (id) + ); + """) + + +def downgrade(): + op.execute('DROP TABLE assoc_detail_sondernutzung') + op.execute('DROP TABLE assoc_detail_zweckgruen') + + op.execute("DROP TABLE codelist_values CASCADE") + op.execute("DROP TABLE codelist") + + op.execute('ALTER TABLE bp_plan DROP COLUMN "sonstPlanArt_id";') + op.execute('ALTER TABLE bp_plan DROP COLUMN "status_id";') + op.execute('ALTER TABLE bp_baugebiet DROP COLUMN "detaillierteArtDerBaulNutzung_id"') + op.execute('ALTER TABLE bp_dachgestaltung DROP COLUMN "detaillierteDachform_id"') + op.execute('ALTER TABLE fp_plan DROP COLUMN "sonstPlanArt_id"') + op.execute('ALTER TABLE fp_plan DROP COLUMN "status_id"') + op.execute('ALTER TABLE fp_baugebiet DROP COLUMN "detaillierteArtDerBaulNutzung_id"') + + op.execute('ALTER TABLE so_denkmalschutz DROP COLUMN "detailArtDerFestlegung_id"') + op.execute('ALTER TABLE so_schienenverkehr DROP COLUMN "detailArtDerFestlegung_id"') + + op.execute(""" + DROP TABLE xp_hoehenangabe CASCADE; + DROP TYPE xp_arthoehenbezug CASCADE; + DROP TYPE xp_arthoehenbezugspunkt CASCADE; + """) + + op.execute(""" + DROP TABLE bp_nutzungsgrenze; + DROP TYPE bp_abgrenzungentypen CASCADE; + """) + + op.execute(""" + DROP TABLE bp_keine_ein_ausfahrt; + DROP TYPE bp_bereichohneeinausfahrttypen CASCADE; + """) + + op.execute(""" + DROP TABLE bp_einfahrtpunkt; + DROP TYPE bp_einfahrttypen CASCADE; + """) diff --git a/alembic/versions/cfbf4d3b2a9d_v1_3_0.py b/alembic/versions/cfbf4d3b2a9d_v1_3_0.py new file mode 100644 index 0000000..19dbf77 --- /dev/null +++ b/alembic/versions/cfbf4d3b2a9d_v1_3_0.py @@ -0,0 +1,461 @@ +"""v1.3.0 + +Revision ID: cfbf4d3b2a9d +Revises: +Create Date: 2021-12-03 12:19:36.787492 + +""" +import os +import sys +from geoalchemy2 import Geometry +from sqlalchemy import text + +from alembic import op +import sqlalchemy as sa + +from sqlalchemy.dialects import postgresql + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + +# revision identifiers, used by Alembic. +revision = 'cfbf4d3b2a9d' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute(text("CREATE EXTENSION IF NOT EXISTS postgis;")) + op.create_table('xp_bereich', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('type', sa.String(length=50), nullable=True), + sa.Column('srs', sa.String(length=20), nullable=True), + sa.Column('nummer', sa.Integer(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('bedeutung', + sa.Enum('Teilbereich', 'Kompensationsbereich', 'Sonstiges', name='xp_bedeutungenbereich'), + nullable=True), + sa.Column('detaillierteBedeutung', sa.String(), nullable=True), + sa.Column('erstellungsMassstab', sa.Integer(), nullable=True), + sa.Column('geltungsbereich', + Geometry(geometry_type='MULTIPOLYGON', from_text='ST_GeomFromEWKT', + name='geometry'), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('xp_gemeinde', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('ags', sa.String(), nullable=True), + sa.Column('rs', sa.String(), nullable=True), + sa.Column('gemeindeName', sa.String(), nullable=False), + sa.Column('ortsteilName', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('xp_plangeber', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('kennziffer', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('xp_objekt', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('type', sa.String(length=50), nullable=True), + sa.Column('uuid', sa.String(), nullable=True), + sa.Column('text', sa.String(), nullable=True), + sa.Column('rechtsstand', sa.Enum('Geplant', 'Bestehend', 'Fortfallend', name='xp_rechtsstand'), + nullable=True), + sa.Column('gesetzlicheGrundlage', sa.Enum('', name='xp_gesetzliche_grundlage'), nullable=True), + sa.Column('gliederung1', sa.String(), nullable=True), + sa.Column('gliederung2', sa.String(), nullable=True), + sa.Column('ebene', sa.Integer(), nullable=True), + sa.Column('gehoertZuBereich_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.Column('aufschrift', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['gehoertZuBereich_id'], ['xp_bereich.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('xp_plan', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('type', sa.String(length=50), nullable=True), + sa.Column('srs', sa.String(length=20), nullable=True), + sa.Column('name', sa.String(), nullable=False), + sa.Column('nummer', sa.String(), nullable=True), + sa.Column('internalId', sa.String(), nullable=True), + sa.Column('beschreibung', sa.String(), nullable=True), + sa.Column('kommentar', sa.String(), nullable=True), + sa.Column('technHerstellDatum', sa.Date(), nullable=True), + sa.Column('genehmigungsDatum', sa.Date(), nullable=True), + sa.Column('untergangsDatum', sa.Date(), nullable=True), + sa.Column('erstellungsMassstab', sa.Integer(), nullable=True), + sa.Column('bezugshoehe', sa.Float(), nullable=True), + sa.Column('technischerPlanersteller', sa.String(), nullable=True), + sa.Column('raeumlicherGeltungsbereich', + Geometry(geometry_type='MULTIPOLYGON', from_text='ST_GeomFromEWKT', + name='geometry'), nullable=True), + sa.Column('plangeber_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.ForeignKeyConstraint(['plangeber_id'], ['xp_plangeber.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('xp_po', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('gehoertZuBereich_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.Column('type', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['gehoertZuBereich_id'], ['xp_bereich.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('xp_simple_geometry', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('xplanung_type', sa.String(), nullable=True), + sa.Column('position', Geometry(from_text='ST_GeomFromEWKT', name='geometry'), + nullable=True), + sa.Column('gehoertZuBereich_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.ForeignKeyConstraint(['gehoertZuBereich_id'], ['xp_bereich.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_objekt', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('rechtscharakter', + sa.Enum('Festsetzung', 'NachrichtlicheUebernahme', 'Hinweis', 'Vermerk', 'Kennzeichnung', + 'Unbekannt', name='bp_rechtscharakter'), nullable=True), + sa.Column('position', Geometry(from_text='ST_GeomFromEWKT', name='geometry'), + nullable=True), + sa.Column('flaechenschluss', sa.Boolean(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['xp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_plan', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('planArt', + sa.Enum('BPlan', 'EinfacherBPlan', 'QualifizierterBPlan', 'VorhabenbezogenerBPlan', + 'VorhabenUndErschliessungsplan', 'InnenbereichsSatzung', 'KlarstellungsSatzung', + 'EntwicklungsSatzung', 'ErgaenzungsSatzung', 'AussenbereichsSatzung', + 'OertlicheBauvorschrift', 'Sonstiges', name='bp_planart'), nullable=False), + sa.Column('verfahren', sa.Enum('Normal', 'Parag13', 'Parag13a', 'Parag13b', name='bp_verfahren'), + nullable=True), + sa.Column('rechtsstand', + sa.Enum('Aufstellungsbeschluss', 'Entwurf', 'FruehzeitigeBehoerdenBeteiligung', + 'FruehzeitigeOeffentlichkeitsBeteiligung', 'BehoerdenBeteiligung', + 'OeffentlicheAuslegung', 'Satzung', 'InkraftGetreten', 'TeilweiseUntergegangen', + 'Untergegangen', 'Aufgehoben', 'AusserKraft', name='bp_rechtsstand'), + nullable=True), + sa.Column('hoehenbezug', sa.String(), nullable=True), + sa.Column('aenderungenBisDatum', sa.Date(), nullable=True), + sa.Column('aufstellungsbeschlussDatum', sa.Date(), nullable=True), + sa.Column('veraenderungssperreBeschlussDatum', sa.Date(), nullable=True), + sa.Column('veraenderungssperreDatum', sa.Date(), nullable=True), + sa.Column('veraenderungssperreEndDatum', sa.Date(), nullable=True), + sa.Column('verlaengerungVeraenderungssperre', + sa.Enum('Keine', 'ErsteVerlaengerung', 'ZweiteVerlaengerung', + name='xp_verlaengerungveraenderungssperre'), nullable=True), + sa.Column('auslegungsStartDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('auslegungsEndDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('traegerbeteiligungsStartDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('traegerbeteiligungsEndDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('satzungsbeschlussDatum', sa.Date(), nullable=True), + sa.Column('rechtsverordnungsDatum', sa.Date(), nullable=True), + sa.Column('inkrafttretensDatum', sa.Date(), nullable=True), + sa.Column('ausfertigungsDatum', sa.Date(), nullable=True), + sa.Column('veraenderungssperre', sa.Boolean(), nullable=True), + sa.Column('staedtebaulicherVertrag', sa.Boolean(), nullable=True), + sa.Column('erschliessungsVertrag', sa.Boolean(), nullable=True), + sa.Column('durchfuehrungsVertrag', sa.Boolean(), nullable=True), + sa.Column('gruenordnungsplan', sa.Boolean(), nullable=True), + sa.Column('versionBauNVODatum', sa.Date(), nullable=True), + sa.Column('versionBauNVOText', sa.String(), nullable=True), + sa.Column('versionBauGBDatum', sa.Date(), nullable=True), + sa.Column('versionBauGBText', sa.String(), nullable=True), + sa.Column('versionSonstRechtsgrundlageDatum', sa.Date(), nullable=True), + sa.Column('versionSonstRechtsgrundlageText', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['xp_plan.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('fp_plan', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('planArt', + sa.Enum('FPlan', 'GemeinsamerFPlan', 'RegFPlan', 'FPlanRegPlan', 'SachlicherTeilplan', + 'Sonstiges', name='fp_planart'), nullable=False), + sa.Column('sachgebiet', sa.String(), nullable=True), + sa.Column('verfahren', sa.Enum('Normal', 'Parag13', name='fp_verfahren'), nullable=True), + sa.Column('rechtsstand', + sa.Enum('Aufstellungsbeschluss', 'Entwurf', 'FruehzeitigeBehoerdenBeteiligung', + 'FruehzeitigeOeffentlichkeitsBeteiligung', 'BehoerdenBeteiligung', + 'OeffentlicheAuslegung', 'Plan', 'Wirksamkeit', 'Untergegangen', 'Aufgehoben', + 'AusserKraft', name='fp_rechtsstand'), nullable=True), + sa.Column('aufstellungsbeschlussDatum', sa.Date(), nullable=True), + sa.Column('auslegungsStartDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('auslegungsEndDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('traegerbeteiligungsStartDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('traegerbeteiligungsEndDatum', sa.ARRAY(sa.Date()), nullable=True), + sa.Column('aenderungenBisDatum', sa.Date(), nullable=True), + sa.Column('entwurfsbeschlussDatum', sa.Date(), nullable=True), + sa.Column('planbeschlussDatum', sa.Date(), nullable=True), + sa.Column('wirksamkeitsDatum', sa.Date(), nullable=True), + sa.Column('versionBauNVODatum', sa.Date(), nullable=True), + sa.Column('versionBauNVOText', sa.String(), nullable=True), + sa.Column('versionBauGBDatum', sa.Date(), nullable=True), + sa.Column('versionBauGBText', sa.String(), nullable=True), + sa.Column('versionSonstRechtsgrundlageDatum', sa.Date(), nullable=True), + sa.Column('versionSonstRechtsgrundlageText', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['xp_plan.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('xp_plan_gemeinde', + sa.Column('plan_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.Column('gemeinde_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.ForeignKeyConstraint(['gemeinde_id'], ['xp_gemeinde.id'], ), + sa.ForeignKeyConstraint(['plan_id'], ['xp_plan.id'], ) + ) + op.create_table('xp_ppo', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('position', Geometry(geometry_type='POINT', from_text='ST_GeomFromEWKT', + name='geometry'), nullable=True), + sa.Column('drehwinkel', sa.Integer(), nullable=True), + sa.Column('skalierung', sa.Float(), nullable=True), + sa.Column('symbol_path', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['xp_po.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('xp_spez_externe_referenz', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('georefURL', sa.String(), nullable=True), + sa.Column('art', sa.Enum('Dokument', 'PlanMitGeoreferenz', name='xp_externereferenzart'), + nullable=True), + sa.Column('referenzName', sa.String(), nullable=True), + sa.Column('referenzURL', sa.String(), nullable=True), + sa.Column('referenzMimeType', + sa.Enum('application/pdf', 'application/zip', 'application/xml', 'application/msword', + 'application/msexcel', 'application/vnd.ogc.sld+xml', + 'application/vnd.ogc.wms_xml', 'application/vnd.ogc.gml', 'application/vnd.shp', + 'application/vnd.dbf', 'application/vnd.shx', 'application/octet-stream', + 'image/vnd.dxf', 'image/vnd.dwg', 'image/jpg', 'image/png', 'image/tiff', + 'image/bmp', 'image/ecw', 'image/svg+xml', 'text/html', 'text/plain', + name='xp_mime_types'), nullable=True), + sa.Column('beschreibung', sa.String(), nullable=True), + sa.Column('datum', sa.Date(), nullable=True), + sa.Column('file', postgresql.BYTEA(), nullable=True), + sa.Column('typ', sa.Enum('Beschreibung', 'Begruendung', 'Legende', 'Rechtsplan', 'Plangrundlage', + 'Umweltbericht', 'Satzung', 'Verordnung', 'Karte', 'Erlaeuterung', + 'ZusammenfassendeErklaerung', 'Koordinatenliste', + 'Grundstuecksverzeichnis', 'Pflanzliste', 'Gruenordnungsplan', + 'Erschliessungsvertrag', 'Durchfuehrungsvertrag', + 'StaedtebaulicherVertrag', 'UmweltbezogeneStellungnahmen', 'Beschluss', + 'VorhabenUndErschliessungsplan', 'MetadatenPlan', 'Genehmigung', + 'Bekanntmachung', 'Rechtsverbindlich', 'Informell', + name='xp_externereferenztyp'), nullable=True), + sa.Column('plan_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.CheckConstraint('NOT("referenzName" IS NULL AND "referenzURL" IS NULL)'), + sa.ForeignKeyConstraint(['plan_id'], ['xp_plan.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('xp_tpo', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('position', Geometry(geometry_type='POINT', from_text='ST_GeomFromEWKT', + name='geometry'), nullable=True), + sa.Column('drehwinkel', sa.Integer(), nullable=True), + sa.Column('schriftinhalt', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['xp_po.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('xp_verfahrens_merkmal', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('plan_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.Column('vermerk', sa.String(), nullable=False), + sa.Column('datum', sa.Date(), nullable=False), + sa.Column('signatur', sa.String(), nullable=False), + sa.Column('signiert', sa.Boolean(), nullable=False), + sa.ForeignKeyConstraint(['plan_id'], ['xp_plan.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_baugebiet', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('FR', sa.Integer(), nullable=True), + sa.Column('MaxZahlWohnungen', sa.Integer(), nullable=True), + sa.Column('MinGRWohneinheit', sa.Float(), nullable=True), + sa.Column('Fmin', sa.Float(), nullable=True), + sa.Column('Fmax', sa.Float(), nullable=True), + sa.Column('Bmin', sa.Float(), nullable=True), + sa.Column('Bmax', sa.Float(), nullable=True), + sa.Column('Tmin', sa.Float(), nullable=True), + sa.Column('Tmax', sa.Float(), nullable=True), + sa.Column('GFZmin', sa.Float(), nullable=True), + sa.Column('GFZmax', sa.Float(), nullable=True), + sa.Column('GFZ', sa.Float(), nullable=True), + sa.Column('GFZ_Ausn', sa.Float(), nullable=True), + sa.Column('GFmin', sa.Float(), nullable=True), + sa.Column('GFmax', sa.Float(), nullable=True), + sa.Column('GF', sa.Float(), nullable=True), + sa.Column('GF_Ausn', sa.Float(), nullable=True), + sa.Column('BMZ', sa.Float(), nullable=True), + sa.Column('BMZ_Ausn', sa.Float(), nullable=True), + sa.Column('BM', sa.Float(), nullable=True), + sa.Column('BM_Ausn', sa.Float(), nullable=True), + sa.Column('GRZmin', sa.Float(), nullable=True), + sa.Column('GRZmax', sa.Float(), nullable=True), + sa.Column('GRZ', sa.Float(), nullable=True), + sa.Column('GRZ_Ausn', sa.Float(), nullable=True), + sa.Column('GRmin', sa.Float(), nullable=True), + sa.Column('GRmax', sa.Float(), nullable=True), + sa.Column('GR', sa.Float(), nullable=True), + sa.Column('GR_Ausn', sa.Float(), nullable=True), + sa.Column('Zmin', sa.Integer(), nullable=True), + sa.Column('Zmax', sa.Integer(), nullable=True), + sa.Column('Zzwingend', sa.Integer(), nullable=True), + sa.Column('Z', sa.Integer(), nullable=True), + sa.Column('Z_Ausn', sa.Integer(), nullable=True), + sa.Column('Z_Staffel', sa.Integer(), nullable=True), + sa.Column('Z_Dach', sa.Integer(), nullable=True), + sa.Column('ZUmin', sa.Integer(), nullable=True), + sa.Column('ZUmax', sa.Integer(), nullable=True), + sa.Column('ZUzwingend', sa.Integer(), nullable=True), + sa.Column('ZU', sa.Integer(), nullable=True), + sa.Column('ZU_Ausn', sa.Integer(), nullable=True), + sa.Column('wohnnutzungEGStrasse', + sa.Enum('Zulaessig', 'NichtZulaessig', 'AusnahmsweiseZulaessig', name='bp_zulaessigkeit'), + nullable=True), + sa.Column('ZWohn', sa.Integer(), nullable=True), + sa.Column('GFAntWohnen', sa.Integer(), nullable=True), + sa.Column('GFWohnen', sa.Float(), nullable=True), + sa.Column('GFAntGewerbe', sa.Integer(), nullable=True), + sa.Column('GFGewerbe', sa.Float(), nullable=True), + sa.Column('VF', sa.Float(), nullable=True), + sa.Column('allgArtDerBaulNutzung', + sa.Enum('WohnBauflaeche', 'GemischteBauflaeche', 'GewerblicheBauflaeche', + 'SonderBauflaeche', 'Sonstiges', name='xp_allgartderbaulnutzung'), nullable=True), + sa.Column('besondereArtDerBaulNutzung', + sa.Enum('Kleinsiedlungsgebiet', 'ReinesWohngebiet', 'AllgWohngebiet', + 'BesonderesWohngebiet', 'Dorfgebiet', 'Mischgebiet', 'UrbanesGebiet', + 'Kerngebiet', 'Gewerbegebiet', 'Industriegebiet', 'SondergebietErholung', + 'SondergebietSonst', 'Wochenendhausgebiet', 'Sondergebiet', 'SonstigesGebiet', + name='xp_besondereartderbaulnutzung'), nullable=True), + sa.Column('sondernutzung', sa.Enum('KeineSondernutzung', 'Wochendhausgebiet', 'Ferienhausgebiet', + 'Campingplatzgebiet', 'Kurgebiet', 'SonstSondergebietErholung', + 'Einzelhandelsgebiet', 'GrossflaechigerEinzelhandel', + 'Ladengebiet', 'Einkaufszentrum', 'SonstGrossflEinzelhandel', + 'Verkehrsuebungsplatz', 'Hafengebiet', + 'SondergebietErneuerbareEnergie', 'SondergebietMilitaer', + 'SondergebietLandwirtschaft', 'SondergebietSport', + 'SondergebietGesundheitSoziales', 'Klinikgebiet', 'Golfplatz', + 'SondergebietKultur', 'SondergebietTourismus', + 'SondergebietBueroUndVerwaltung', 'SondergebietJustiz', + 'SondergebietHochschuleForschung', 'SondergebietMesse', + 'SondergebietAndereNutzungen', name='xp_sondernutzungen'), + nullable=True), + sa.Column('nutzungText', sa.String(), nullable=True), + sa.Column('abweichungBauNVO', + sa.Enum('KeineAbweichung', 'EinschraenkungNutzung', 'AusschlussNutzung', + 'AusweitungNutzung', 'SonstAbweichung', name='xp_abweichungbaunvotypen'), + nullable=True), + sa.Column('bauweise', + sa.Enum('KeineAngabe', 'OffeneBauweise', 'GeschlosseneBauweise', 'AbweichendeBauweise', + name='bp_bauweise'), nullable=True), + sa.Column('vertikaleDifferenzierung', sa.Boolean(), nullable=True), + sa.Column('bebauungsArt', + sa.Enum('Einzelhaeuser', 'Doppelhaeuser', 'Hausgruppen', 'EinzelDoppelhaeuser', + 'EinzelhaeuserHausgruppen', 'DoppelhaeuserHausgruppen', 'Reihenhaeuser', + 'EinzelhaeuserDoppelhaeuserHausgruppen', name='bp_bebauungsart'), nullable=True), + sa.Column('bebauungVordereGrenze', + sa.Enum('KeineAngabe', 'Verboten', 'Erlaubt', 'Erzwungen', name='bp_grenzbebauung'), + nullable=True), + sa.Column('bebauungRueckwaertigeGrenze', + sa.Enum('KeineAngabe', 'Verboten', 'Erlaubt', 'Erzwungen', name='bp_grenzbebauung'), + nullable=True), + sa.Column('bebauungSeitlicheGrenze', + sa.Enum('KeineAngabe', 'Verboten', 'Erlaubt', 'Erzwungen', name='bp_grenzbebauung'), + nullable=True), + sa.Column('zugunstenVon', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_baugrenze', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('bautiefe', sa.Float(), nullable=True), + sa.Column('geschossMin', sa.Integer(), nullable=True), + sa.Column('geschossMax', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bp_objekt.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_bereich', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('versionBauGBDatum', sa.Date(), nullable=True), + sa.Column('versionBauGBText', sa.String(), nullable=True), + sa.Column('versionSonstRechtsgrundlageDatum', sa.Date(), nullable=True), + sa.Column('versionSonstRechtsgrundlageText', sa.String(), nullable=True), + sa.Column('gehoertZuPlan_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.ForeignKeyConstraint(['gehoertZuPlan_id'], ['bp_plan.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['id'], ['xp_bereich.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('fp_bereich', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('gehoertZuPlan_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.ForeignKeyConstraint(['gehoertZuPlan_id'], ['fp_plan.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['id'], ['xp_bereich.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('bp_dachgestaltung', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('DNmin', sa.Integer(), nullable=True), + sa.Column('DNmax', sa.Integer(), nullable=True), + sa.Column('DN', sa.Integer(), nullable=True), + sa.Column('DNZwingend', sa.Integer(), nullable=True), + sa.Column('dachform', + sa.Enum('Flachdach', 'Pultdach', 'VersetztesPultdach', 'GeneigtesDach', 'Satteldach', + 'Walmdach', 'KrueppelWalmdach', 'Mansarddach', 'Zeltdach', 'Kegeldach', + 'Kuppeldach', 'Sheddach', 'Bogendach', 'Turmdach', 'Tonnendach', 'Mischform', + 'Sonstiges', name='bp_dachform'), nullable=True), + sa.Column('baugebiet_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.ForeignKeyConstraint(['baugebiet_id'], ['bp_baugebiet.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('xp_externe_referenz', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('georefURL', sa.String(), nullable=True), + sa.Column('art', sa.Enum('Dokument', 'PlanMitGeoreferenz', name='xp_externereferenzart'), + nullable=True), + sa.Column('referenzName', sa.String(), nullable=True), + sa.Column('referenzURL', sa.String(), nullable=True), + sa.Column('referenzMimeType', + sa.Enum('application/pdf', 'application/zip', 'application/xml', 'application/msword', + 'application/msexcel', 'application/vnd.ogc.sld+xml', + 'application/vnd.ogc.wms_xml', 'application/vnd.ogc.gml', 'application/vnd.shp', + 'application/vnd.dbf', 'application/vnd.shx', 'application/octet-stream', + 'image/vnd.dxf', 'image/vnd.dwg', 'image/jpg', 'image/png', 'image/tiff', + 'image/bmp', 'image/ecw', 'image/svg+xml', 'text/html', 'text/plain', + name='xp_mime_types'), nullable=True), + sa.Column('beschreibung', sa.String(), nullable=True), + sa.Column('datum', sa.Date(), nullable=True), + sa.Column('file', postgresql.BYTEA(), nullable=True), + sa.Column('bereich_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.Column('baugebiet_id', postgresql.UUID(as_uuid=True), nullable=True), + sa.CheckConstraint('NOT("referenzName" IS NULL AND "referenzURL" IS NULL)'), + sa.ForeignKeyConstraint(['baugebiet_id'], ['bp_baugebiet.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['bereich_id'], ['xp_bereich.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + + +def downgrade(): + op.drop_table('xp_externe_referenz') + op.drop_table('bp_dachgestaltung') + op.drop_table('fp_bereich') + op.drop_table('bp_bereich') + op.drop_table('bp_baugrenze') + op.drop_table('bp_baugebiet') + op.drop_table('xp_verfahrens_merkmal') + op.drop_table('xp_tpo') + op.drop_table('xp_spez_externe_referenz') + op.drop_table('xp_ppo') + op.drop_table('xp_plan_gemeinde') + op.drop_table('fp_plan') + op.drop_table('bp_plan') + op.drop_table('bp_objekt') + op.drop_table('xp_simple_geometry') + op.drop_table('xp_po') + op.drop_table('xp_plan') + op.drop_table('xp_objekt') + op.drop_table('xp_plangeber') + op.drop_table('xp_gemeinde') + op.drop_table('xp_bereich') diff --git a/alembic/versions/fb27f7a59e17_v2_0_0.py b/alembic/versions/fb27f7a59e17_v2_0_0.py new file mode 100644 index 0000000..b6d53fd --- /dev/null +++ b/alembic/versions/fb27f7a59e17_v2_0_0.py @@ -0,0 +1,1371 @@ +"""v2.0.0 + +Revision ID: fb27f7a59e17 +Revises: b93ec8372df6 +Create Date: 2023-04-14 12:16:25.596655 + +""" +import os +import sys + +from sqlalchemy.dialects import postgresql + +from alembic import op +import sqlalchemy as sa + + + +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) + + +# revision identifiers, used by Alembic. +revision = 'fb27f7a59e17' +down_revision = 'b93ec8372df6' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute("ALTER TABLE xp_plan ADD COLUMN hoehenbezug VARCHAR;") + + # CR-029: create trigger to keep `hoehenbezug` attribute in sync + op.execute(""" + create or replace function xp_plan_sync_attr_hoehenbezug() + returns trigger language plpgsql as $$ + begin + UPDATE xp_plan + SET hoehenbezug = COALESCE(NEW.hoehenbezug, hoehenbezug) + where xp_plan.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger xp_plan_sync_attr_hoehenbezug + after insert or update on bp_plan + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure xp_plan_sync_attr_hoehenbezug(); + + create or replace function bp_plan_sync_attr_hoehenbezug() + returns trigger language plpgsql as $$ + begin + UPDATE bp_plan + SET hoehenbezug = COALESCE(NEW.hoehenbezug, hoehenbezug) + where bp_plan.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger bp_plan_sync_attr_hoehenbezug + after insert or update on xp_plan + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_plan_sync_attr_hoehenbezug(); + """) + + # CR-026 + op.drop_column('xp_objekt', 'gesetzlicheGrundlage') + op.execute("DROP TYPE xp_gesetzliche_grundlage CASCADE") + op.add_column('xp_objekt', sa.Column('gesetzlicheGrundlage_id', postgresql.UUID(as_uuid=True), nullable=True)) + op.create_table('xp_gesetzliche_grundlage', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('name', sa.VARCHAR(), nullable=True), + sa.Column('datum', sa.Date(), nullable=True), + sa.Column('detail', sa.VARCHAR(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + op.execute("ALTER TYPE xp_externereferenztyp ADD VALUE 'Schutzgebietsverordnung';") + + # CR-025 + op.execute("CREATE TYPE xp_traegerschaft AS ENUM('EinrichtungBund','EinrichtungLand', 'EinrichtungKreis', 'KommunaleEinrichtung', 'ReligioeserTraeger', 'SonstTraeger');") + op.execute("ALTER TABLE bp_gemeinbedarf ADD COLUMN traeger xp_traegerschaft;") + + # CR-034 + op.execute('UPDATE xp_externe_referenz SET "referenzName"=\'Unbekannt\' WHERE "referenzName" is NULL;') + op.execute('UPDATE xp_externe_referenz SET "referenzURL"="referenzURL" WHERE "referenzURL" is NULL;') + + # CR-023 + op.execute("ALTER TYPE xp_sondernutzungen ADD VALUE 'SondergebietGrosshandel';") + + # CR-048, CR-059 + + op.execute("ALTER TYPE xp_spemassnahmentypen ADD VALUE 'ArtenreicherGehoelzbestand';") + + # The previous statement has to be committed in order for the update to work. + # This is a bit inconvenient as the whole transaction is now split. So if latter part fails the first migrations + # are still run. + with op.get_context().autocommit_block(): + op.execute('UPDATE xp_spe_daten SET "klassifizMassnahme" = \'ArtenreicherGehoelzbestand\'::xp_spemassnahmentypen WHERE "klassifizMassnahme" = \'ArtentreicherGehoelzbestand\'::xp_spemassnahmentypen;') + + op.execute("ALTER TYPE xp_spemassnahmentypen ADD VALUE 'Moor';") + + # CR-014: create trigger to keep `verfahren` attribute in sync + op.execute("ALTER TABLE bp_bereich ADD COLUMN verfahren bp_verfahren;") + op.execute('UPDATE bp_bereich SET verfahren = bp_plan.verfahren FROM bp_plan WHERE "gehoertZuPlan_id" = bp_plan.id;') + op.execute(""" + create or replace function bp_bereich_sync_attr_verfahren() + returns trigger language plpgsql as $$ + begin + UPDATE bp_bereich + SET verfahren = COALESCE(NEW.verfahren, bp_bereich.verfahren) + where bp_bereich.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger bp_bereich_sync_attr_verfahren + after insert or update on bp_plan + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_bereich_sync_attr_verfahren(); + + create or replace function bp_plan_sync_attr_verfahren() + returns trigger language plpgsql as $$ + begin + UPDATE bp_plan + SET verfahren = COALESCE(NEW.verfahren, verfahren) + where bp_plan.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger bp_plan_sync_attr_verfahren + after insert or update on bp_bereich + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_plan_sync_attr_verfahren(); + """) + + # CR-40: Rechtscharakter + op.execute(""" + CREATE TYPE xp_rechtscharakter AS ENUM ( + 'FestsetzungBPlan', + 'NachrichtlicheUebernahme', + 'DarstellungFPlan', + 'ZielDerRaumordnung', + 'GrundsatzDerRaumordnung', + 'NachrichtlicheUebernahmeZiel', + 'NachrichtlicheUebernahmeGrundsatz', + 'NurInformationsgehaltRPlan', + 'TextlichesZielRaumordnung', + 'ZielUndGrundsatzRaumordnung', + 'VorschlagRaumordnung', + 'FestsetzungImLP', + 'GeplanteFestsetzungImLP', + 'DarstellungKennzeichnungImLP', + 'LandschaftsplanungsInhaltZurBeruecksichtigung', + 'Hinweis', + 'Kennzeichnung', + 'Vermerk', + 'Unbekannt', + 'Sonstiges'); + + ALTER TABLE xp_objekt ADD COLUMN rechtscharakter xp_rechtscharakter; + -- bp_objekt + UPDATE xp_objekt SET rechtscharakter = ( + CASE + WHEN bp_objekt.rechtscharakter = 'Festsetzung'::bp_rechtscharakter + THEN 'FestsetzungBPlan'::xp_rechtscharakter + ELSE bp_objekt.rechtscharakter::text::xp_rechtscharakter + END) + FROM bp_objekt WHERE xp_objekt.id = bp_objekt.id; + + create or replace function xp_objekt_sync_attr_rechtscharakter() + returns trigger language plpgsql as $$ + begin + UPDATE xp_objekt + SET rechtscharakter = ( + CASE + WHEN NEW.rechtscharakter = 'Festsetzung'::bp_rechtscharakter + THEN 'FestsetzungBPlan'::xp_rechtscharakter + ELSE COALESCE(NEW.rechtscharakter::text::xp_rechtscharakter, rechtscharakter) + END) + where xp_objekt.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger xp_objekt_sync_attr_rechtscharakter + after insert or update on bp_objekt + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure xp_objekt_sync_attr_rechtscharakter(); + + create or replace function bp_objekt_sync_attr_rechtscharakter() + returns trigger language plpgsql as $$ + begin + IF new.type != 'bp_objekt' + THEN RETURN NEW; + END IF; + UPDATE bp_objekt + SET rechtscharakter = ( + CASE + WHEN NEW.rechtscharakter = 'FestsetzungBPlan'::xp_rechtscharakter + THEN 'Festsetzung'::bp_rechtscharakter + WHEN NEW.rechtscharakter in ('NachrichtlicheUebernahme'::xp_rechtscharakter, + 'Hinweis'::xp_rechtscharakter, + 'Vermerk'::xp_rechtscharakter, + 'Kennzeichnung'::xp_rechtscharakter, + 'Unbekannt'::xp_rechtscharakter) + THEN NEW.rechtscharakter::text::bp_rechtscharakter + ELSE rechtscharakter + END) + where bp_objekt.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger bp_objekt_sync_attr_hoehenbezug + after insert or update on xp_objekt + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_objekt_sync_attr_rechtscharakter(); + + + -- fp_objekt + UPDATE xp_objekt SET rechtscharakter = ( + CASE + WHEN fp_objekt.rechtscharakter = 'Darstellung'::fp_rechtscharakter + THEN 'DarstellungFPlan'::xp_rechtscharakter + ELSE fp_objekt.rechtscharakter::text::xp_rechtscharakter + END) + FROM fp_objekt WHERE xp_objekt.id = fp_objekt.id; + + create or replace function xp_objekt_sync_attr_rechtscharakter_from_fp() + returns trigger language plpgsql as $$ + begin + UPDATE xp_objekt + SET rechtscharakter = ( + CASE + WHEN NEW.rechtscharakter = 'Darstellung'::fp_rechtscharakter + THEN 'DarstellungFPlan'::xp_rechtscharakter + ELSE COALESCE(NEW.rechtscharakter::text::xp_rechtscharakter, rechtscharakter) + END) + where xp_objekt.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger xp_objekt_sync_attr_rechtscharakter_from_fp + after insert or update on fp_objekt + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure xp_objekt_sync_attr_rechtscharakter_from_fp(); + + create or replace function fp_objekt_sync_attr_rechtscharakter() + returns trigger language plpgsql as $$ + begin + IF new.type != 'fp_objekt' + THEN RETURN NEW; + END IF; + UPDATE fp_objekt + SET rechtscharakter = ( + CASE + WHEN NEW.rechtscharakter = 'DarstellungFPlan'::xp_rechtscharakter + THEN 'Darstellung'::fp_rechtscharakter + WHEN NEW.rechtscharakter in ('NachrichtlicheUebernahme'::xp_rechtscharakter, + 'Hinweis'::xp_rechtscharakter, + 'Vermerk'::xp_rechtscharakter, + 'Kennzeichnung'::xp_rechtscharakter, + 'Unbekannt'::xp_rechtscharakter) + THEN NEW.rechtscharakter::text::fp_rechtscharakter + ELSE rechtscharakter + END) + where fp_objekt.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger fp_objekt_sync_attr_rechtscharakter + after insert or update on xp_objekt + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_objekt_sync_attr_rechtscharakter(); + + + -- so_objekt + UPDATE xp_objekt SET rechtscharakter = ( + CASE + WHEN so_objekt.rechtscharakter = 'InhaltLPlan'::so_rechtscharakter + THEN 'FestsetzungImLP'::xp_rechtscharakter + ELSE so_objekt.rechtscharakter::text::xp_rechtscharakter + END) + FROM so_objekt WHERE xp_objekt.id = so_objekt.id; + + create or replace function xp_objekt_sync_attr_rechtscharakter_from_so() + returns trigger language plpgsql as $$ + begin + UPDATE xp_objekt + SET rechtscharakter = ( + CASE + WHEN NEW.rechtscharakter = 'InhaltLPlan'::so_rechtscharakter + THEN 'FestsetzungImLP'::xp_rechtscharakter + ELSE COALESCE(NEW.rechtscharakter::text::xp_rechtscharakter, rechtscharakter) + END) + where xp_objekt.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger xp_objekt_sync_attr_rechtscharakter_from_so + after insert or update on so_objekt + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure xp_objekt_sync_attr_rechtscharakter_from_so(); + + create or replace function so_objekt_sync_attr_rechtscharakter() + returns trigger language plpgsql as $$ + begin + IF new.type != 'so_objekt' + THEN RETURN NEW; + END IF; + UPDATE so_objekt + SET rechtscharakter = ( + CASE + WHEN NEW.rechtscharakter = 'FestsetzungImLP'::xp_rechtscharakter + THEN 'InhaltLPlan'::so_rechtscharakter + WHEN NEW.rechtscharakter in ('NachrichtlicheUebernahme'::xp_rechtscharakter, + 'Hinweis'::xp_rechtscharakter, + 'Vermerk'::xp_rechtscharakter, + 'Kennzeichnung'::xp_rechtscharakter, + 'Unbekannt'::xp_rechtscharakter, + 'Sonstiges'::xp_rechtscharakter) + THEN NEW.rechtscharakter::text::so_rechtscharakter + ELSE rechtscharakter + END) + where so_objekt.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger so_objekt_sync_attr_rechtscharakter + after insert or update on xp_objekt + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure so_objekt_sync_attr_rechtscharakter(); + """) + + # add enum value 'BebauungsplanZurWohnraumversorgung' to BP_PlanArt + op.execute("ALTER TYPE bp_planart ADD VALUE 'BebauungsplanZurWohnraumversorgung';") + # add enum value 'Naturerfahrungsraum' to XP_ZweckbestimmungGruen + op.execute("ALTER TYPE xp_zweckbestimmunggruen ADD VALUE 'Naturerfahrungsraum';") + # add enum value 'DoerflichesWohngebiet' to XP_BesondereArtDerBaulNutzung + op.execute("ALTER TYPE xp_besondereartderbaulnutzung ADD VALUE 'DoerflichesWohngebiet';") + + # CR-025 für BP_GruenFlaeche + op.execute(""" + CREATE TABLE bp_zweckbestimmung_gruen ( + id UUID NOT NULL, + allgemein xp_zweckbestimmunggruen, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + gruenflaeche_id UUID, + FOREIGN KEY(gruenflaeche_id) REFERENCES bp_gruenflaeche (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM bp_gruenflaeche g + ) + INSERT INTO bp_zweckbestimmung_gruen (id, allgemein, gruenflaeche_id) + SELECT * FROM upd; + + create or replace function bp_gruenflaeche_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE bp_zweckbestimmung_gruen + SET allgemein = NEW.zweckbestimmung + WHERE gruenflaeche_id = NEW.id; + ELSE + UPDATE bp_gruenflaeche SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.gruenflaeche_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_gruenflaeche_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on bp_gruenflaeche + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_gruenflaeche_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger bp_gruenflaeche_sync_attr_zweckbestimmung + after insert or update of allgemein on bp_zweckbestimmung_gruen + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_gruenflaeche_sync_attr_zweckbestimmung('gruenflaeche_id'); + """) + + # CR-025 für BP_KomplexeSondernutzung + op.execute(""" + CREATE TABLE bp_komplexe_sondernutzung ( + id UUID NOT NULL, + allgemein xp_sondernutzungen, + "nutzungText" VARCHAR, + aufschrift VARCHAR, + baugebiet_id UUID, + FOREIGN KEY(baugebiet_id) REFERENCES bp_baugebiet (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + INSERT INTO bp_komplexe_sondernutzung (id, baugebiet_id, allgemein) + SELECT uuid_generate_v4(), bp_baugebiet.id, t.sondernutzung_unnested + FROM bp_baugebiet + CROSS JOIN unnest(bp_baugebiet.sondernutzung) as t(sondernutzung_unnested); + + create or replace function bp_baugebiet_sync_attr_sondernutzung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'sondernutzung' THEN + DELETE FROM bp_komplexe_sondernutzung WHERE baugebiet_id = NEW.id; + + INSERT INTO bp_komplexe_sondernutzung (id, baugebiet_id, allgemein) + SELECT uuid_generate_v4(), bp_baugebiet.id, t.sondernutzung_unnested + FROM bp_baugebiet + CROSS JOIN unnest(bp_baugebiet.sondernutzung) as t(sondernutzung_unnested); + ELSE + -- make sure no duplicates are inserted by selecting distinct from unnested array + UPDATE bp_baugebiet SET sondernutzung = ARRAY(SELECT DISTINCT UNNEST(sondernutzung || ARRAY[NEW.allgemein])) + WHERE id = NEW.baugebiet_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_baugebiet_sync_attr_sondernutzung + after insert or update of sondernutzung on bp_baugebiet + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_baugebiet_sync_attr_sondernutzung('sondernutzung'); + + create constraint trigger bp_baugebiet_sync_attr_sondernutzung + after insert or update of allgemein on bp_komplexe_sondernutzung + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_baugebiet_sync_attr_sondernutzung('baugebiet_id'); + + create or replace function bp_baugebiet_sync_attr_sondernutzung_on_delete() + returns trigger language plpgsql as $$ + begin + -- make sure no duplicates are inserted by selecting distinct from unnested array + UPDATE bp_baugebiet SET sondernutzung = array_remove(sondernutzung, OLD.allgemein) + WHERE id = OLD.baugebiet_id; + RETURN NULL; + end $$; + + create constraint trigger bp_baugebiet_sync_attr_sondernutzung_on_delete + after delete on bp_komplexe_sondernutzung + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_baugebiet_sync_attr_sondernutzung_on_delete(); + """) + + # CR-022 + op.execute(""" + CREATE TYPE so_zweckbestimmungstrassenverkehr AS ENUM ( + 'AutobahnUndAehnlich', + 'Hauptverkehrsstrasse', + 'SonstigerVerkehrswegAnlage', + 'VerkehrsberuhigterBereich', + 'Platz', + 'Fussgaengerbereich', + 'RadGehweg', + 'Radweg', + 'Gehweg', + 'Wanderweg', + 'ReitKutschweg', + 'Rastanlage', + 'Busbahnhof', + 'UeberfuehrenderVerkehrsweg', + 'UnterfuehrenderVerkehrsweg', + 'Wirtschaftsweg', + 'LandwirtschaftlicherVerkehr', + 'Anschlussflaeche', + 'Verkehrsgruen', + 'RuhenderVerkehr', + 'Parkplatz', + 'FahrradAbstellplatz', + 'P_RAnlage', + 'B_RAnlage', + 'Parkhaus', + 'CarSharing', + 'BikeSharing', + 'Mischverkehrsflaeche', + 'Ladestation', + 'Sonstiges'); + + CREATE TYPE so_strasseneinteilung AS ENUM ( + 'Bundesautobahn', + 'Bundesstrasse', + 'LandesStaatsstrasse', + 'Kreisstrasse', + 'Gemeindestrasse', + 'SonstOeffentlStrasse'); + + + CREATE TABLE so_strassenverkehr ( + id UUID NOT NULL, + "MaxZahlWohnungen" INTEGER, + "MinGRWohneinheit" FLOAT, + "Fmin" FLOAT, + "Fmax" FLOAT, + "Bmin" FLOAT, + "Bmax" FLOAT, + "Tmin" FLOAT, + "Tmax" FLOAT, + "GFZmin" FLOAT, + "GFZmax" FLOAT, + "GFZ" FLOAT, + "GFZ_Ausn" FLOAT, + "GFmin" FLOAT, + "GFmax" FLOAT, + "GF" FLOAT, + "GF_Ausn" FLOAT, + "BMZ" FLOAT, + "BMZ_Ausn" FLOAT, + "BM" FLOAT, + "BM_Ausn" FLOAT, + "GRZmin" FLOAT, + "GRZmax" FLOAT, + "GRZ" FLOAT, + "GRZ_Ausn" FLOAT, + "GRmin" FLOAT, + "GRmax" FLOAT, + "GR" FLOAT, + "GR_Ausn" FLOAT, + "Zmin" INTEGER, + "Zmax" INTEGER, + "Zzwingend" INTEGER, + "Z" INTEGER, + "Z_Ausn" INTEGER, + "Z_Staffel" INTEGER, + "Z_Dach" INTEGER, + "ZUmin" INTEGER, + "ZUmax" INTEGER, + "ZUzwingend" INTEGER, + "ZU" INTEGER, + "ZU_Ausn" INTEGER, + + einteilung so_strasseneinteilung, + name VARCHAR, + nummer VARCHAR, + "istOrtsdurchfahrt" BOOLEAN, + nutzungsform xp_nutzungsform not null, + "zugunstenVon" VARCHAR, + "hatDarstellungMitBesondZweckbest" BOOLEAN, + + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES so_objekt (id) ON DELETE CASCADE + ); + + CREATE TABLE so_zweckbestimmung_strassenverkehr ( + id UUID NOT NULL, + allgemein so_zweckbestimmungstrassenverkehr, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + strassenverkehr_id UUID, + FOREIGN KEY(strassenverkehr_id) REFERENCES so_strassenverkehr (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + """) + + # change gml:AngleType column nordwinkel to float + op.execute('ALTER TABLE so_objekt ALTER COLUMN nordwinkel TYPE FLOAT USING nordwinkel::float;') + + # change FP_Gemeinbedarf.zweckbestimmung to array + op.execute("ALTER TABLE fp_gemeinbedarf ALTER zweckbestimmung DROP DEFAULT, ALTER zweckbestimmung type xp_zweckbestimmunggemeinbedarf[] using NULLIF(ARRAY[zweckbestimmung], '{null}'), alter zweckbestimmung set default '{}';") + + # CR-019 + op.execute(""" + ALTER TYPE bp_rechtsstand ADD VALUE 'Entwurfsbeschluss'; + ALTER TYPE bp_rechtsstand ADD VALUE 'TeilweiseAufgehoben'; + ALTER TYPE bp_rechtsstand ADD VALUE 'TeilweiseAusserKraft'; + + ALTER TYPE rp_rechtsstand ADD VALUE 'TeilweiseAusserKraft'; + ALTER TYPE fp_rechtsstand ADD VALUE 'Entwurfsbeschluss'; + """) + + # CR-025 BP_SpielSportanlagenFlaeche + op.execute(""" + CREATE TABLE bp_zweckbestimmung_sport ( + id UUID NOT NULL, + allgemein xp_zweckbestimmungspielsportanlage, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + spiel_sportanlage_id UUID, + FOREIGN KEY(spiel_sportanlage_id) REFERENCES bp_spiel_sportanlage (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM bp_spiel_sportanlage g + ) + INSERT INTO bp_zweckbestimmung_sport (id, allgemein, spiel_sportanlage_id) + SELECT * FROM upd; + + create or replace function bp_spiel_sportanlage_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE bp_zweckbestimmung_sport + SET allgemein = NEW.zweckbestimmung + WHERE spiel_sportanlage_id = NEW.id; + ELSE + UPDATE bp_spiel_sportanlage SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.spiel_sportanlage_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_spiel_sportanlage_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on bp_spiel_sportanlage + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_spiel_sportanlage_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger bp_spiel_sportanlage_sync_attr_zweckbestimmung + after insert or update of allgemein on bp_zweckbestimmung_sport + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_spiel_sportanlage_sync_attr_zweckbestimmung('spiel_sportanlage_id'); + """) + + # CR-025 BP_GemeinbedarfsFlaeche + op.execute(""" + CREATE TABLE bp_zweckbestimmung_gemeinbedarf ( + id UUID NOT NULL, + allgemein xp_zweckbestimmunggemeinbedarf, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + gemeinbedarf_id UUID, + FOREIGN KEY(gemeinbedarf_id) REFERENCES bp_gemeinbedarf (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM bp_gemeinbedarf g + ) + INSERT INTO bp_zweckbestimmung_gemeinbedarf (id, allgemein, gemeinbedarf_id) + SELECT * FROM upd; + + create or replace function bp_gemeinbedarf_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE bp_zweckbestimmung_gemeinbedarf + SET allgemein = NEW.zweckbestimmung + WHERE gemeinbedarf_id = NEW.id; + ELSE + UPDATE bp_gemeinbedarf SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.gemeinbedarf_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_gemeinbedarf_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on bp_gemeinbedarf + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_gemeinbedarf_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger bp_gemeinbedarf_sync_attr_zweckbestimmung + after insert or update of allgemein on bp_zweckbestimmung_gemeinbedarf + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_gemeinbedarf_sync_attr_zweckbestimmung('gemeinbedarf_id'); + """) + + # CR-025 BP_LandwirtschaftsFlaeche + op.execute(""" + CREATE TABLE bp_zweckbestimmung_landwirtschaft ( + id UUID NOT NULL, + allgemein xp_zweckbestimmunglandwirtschaft, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + landwirtschaft_id UUID, + FOREIGN KEY(landwirtschaft_id) REFERENCES bp_landwirtschaft (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM bp_landwirtschaft g + ) + INSERT INTO bp_zweckbestimmung_landwirtschaft (id, allgemein, landwirtschaft_id) + SELECT * FROM upd; + + create or replace function bp_landwirtschaft_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE bp_zweckbestimmung_landwirtschaft + SET allgemein = NEW.zweckbestimmung + WHERE landwirtschaft_id = NEW.id; + ELSE + UPDATE bp_landwirtschaft SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.landwirtschaft_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_landwirtschaft_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on bp_landwirtschaft + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_landwirtschaft_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger bp_landwirtschaft_sync_attr_zweckbestimmung + after insert or update of allgemein on bp_zweckbestimmung_landwirtschaft + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_landwirtschaft_sync_attr_zweckbestimmung('landwirtschaft_id'); + """) + + # CR-025 BP_WaldFlaeche + op.execute(""" + CREATE TABLE bp_zweckbestimmung_wald ( + id UUID NOT NULL, + allgemein xp_zweckbestimmungwald, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + waldflaeche_id UUID, + FOREIGN KEY(waldflaeche_id) REFERENCES bp_wald (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM bp_wald g + ) + INSERT INTO bp_zweckbestimmung_wald (id, allgemein, waldflaeche_id) + SELECT * FROM upd; + + create or replace function bp_wald_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE bp_zweckbestimmung_wald + SET allgemein = NEW.zweckbestimmung + WHERE waldflaeche_id = NEW.id; + ELSE + UPDATE bp_wald SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.waldflaeche_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_wald_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on bp_wald + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_wald_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger bp_wald_sync_attr_zweckbestimmung + after insert or update of allgemein on bp_zweckbestimmung_wald + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_wald_sync_attr_zweckbestimmung('waldflaeche_id'); + """) + + # CR-018 + op.execute('ALTER TABLE bp_pflanzung ADD COLUMN "pflanzenArt" VARCHAR') + + # CR-025 BP_VerEntsorgung + + op.execute(""" + CREATE TABLE bp_zweckbestimmung_versorgung ( + id UUID NOT NULL, + allgemein xp_zweckbestimmungverentsorgung, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + versorgung_id UUID, + FOREIGN KEY(versorgung_id) REFERENCES bp_versorgung (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM bp_versorgung g + ) + INSERT INTO bp_zweckbestimmung_versorgung (id, allgemein, versorgung_id) + SELECT * FROM upd; + + create or replace function bp_versorgung_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE bp_zweckbestimmung_versorgung + SET allgemein = NEW.zweckbestimmung + WHERE versorgung_id = NEW.id; + ELSE + UPDATE bp_versorgung SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.versorgung_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_versorgung_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on bp_versorgung + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_versorgung_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger bp_versorgung_sync_attr_zweckbestimmung + after insert or update of allgemein on bp_zweckbestimmung_versorgung + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_versorgung_sync_attr_zweckbestimmung('versorgung_id'); + """) + + # CR-051 + op.execute(""" + CREATE TYPE bp_verlaengerungveraenderungssperre AS ENUM ('Keine', 'ErsteVerlaengerung', 'ZweiteVerlaengerung'); + + CREATE TABLE bp_veraenderungssperre_daten ( + id UUID NOT NULL, + "startDatum" DATE not null, + "endDatum" DATE not null, + verlaengerung bp_verlaengerungveraenderungssperre, + "beschlussDatum" DATE, + + plan_id UUID, + FOREIGN KEY(plan_id) REFERENCES bp_plan (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + ALTER TABLE xp_externe_referenz ADD COLUMN veraenderungssperre_id UUID REFERENCES bp_veraenderungssperre_daten(id); + + WITH upd AS ( + SELECT uuid_generate_v4(), "veraenderungssperreDatum", + "veraenderungssperreEndDatum", "verlaengerungVeraenderungssperre"::text::bp_verlaengerungveraenderungssperre AS verl, + "veraenderungssperreBeschlussDatum", id FROM bp_plan p + ) + INSERT INTO bp_veraenderungssperre_daten (id, "startDatum", "endDatum", verlaengerung, "beschlussDatum", plan_id) + SELECT * FROM upd + WHERE upd."veraenderungssperreEndDatum" is not NULL AND "veraenderungssperreDatum" is not NULL AND upd.verl is not NULL; + """) + + # CR-057 SO_Gewaesser + + op.execute(""" + CREATE TYPE so_klassifizgewaesser AS ENUM ( + 'Gewaesser', + 'Fliessgewaesser', + 'Gewaesser1Ordnung', + 'Gewaesser2Ordnung', + 'Gewaesser3Ordnung', + 'StehendesGewaesser', + 'Hafen', + 'Sportboothafen', + 'Wasserstrasse', + 'Kanal', + 'Sonstiges'); + + CREATE TABLE so_gewaesser ( + id UUID NOT NULL, + name VARCHAR, + nummer VARCHAR, + + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES so_objekt (id) ON DELETE CASCADE + ); + + CREATE TABLE so_festlegung_gewaesser ( + id UUID NOT NULL, + allgemein so_klassifizgewaesser, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + gewaesser_id UUID, + FOREIGN KEY(gewaesser_id) REFERENCES so_gewaesser (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + """) + + # CR-057 SO_Wasserwirtschaft + op.execute(""" + CREATE TYPE so_klassifizwasserwirtschaft AS ENUM ( + 'HochwasserRueckhaltebecken', + 'Ueberschwemmgebiet', + 'Versickerungsflaeche', + 'Entwaesserungsgraben', + 'Deich', + 'RegenRueckhaltebecken', + 'Sonstiges'); + + CREATE TABLE so_wasserwirtschaft ( + id UUID NOT NULL, + + "artDerFestlegung" so_klassifizwasserwirtschaft, + + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES so_objekt (id) ON DELETE CASCADE + ); + """) + + # CR-039 FP_Plan + op.execute(""" + ALTER TABLE fp_plan ADD COLUMN "versionBauNVO_id" uuid REFERENCES xp_gesetzliche_grundlage(id); + ALTER TABLE fp_plan ADD COLUMN "versionBauGB_id" uuid REFERENCES xp_gesetzliche_grundlage(id); + ALTER TABLE fp_plan ADD COLUMN "versionSonstRechtsgrundlage_id" uuid REFERENCES xp_gesetzliche_grundlage(id); + """) + + # CR-049, CR-051 FP_BebauungsFlaeche + op.execute(""" + ALTER TABLE fp_baugebiet ADD COLUMN "GFZdurchschnittlich" FLOAT; + ALTER TABLE fp_baugebiet ADD COLUMN "abweichungBauNVO" xp_abweichungbaunvotypen; + """) + + # CR-025 FP_BebauungsFlaeche + op.execute(""" + CREATE TABLE fp_komplexe_sondernutzung ( + id UUID NOT NULL, + allgemein xp_sondernutzungen, + "nutzungText" VARCHAR, + aufschrift VARCHAR, + baugebiet_id UUID, + FOREIGN KEY(baugebiet_id) REFERENCES fp_baugebiet (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + INSERT INTO fp_komplexe_sondernutzung (id, baugebiet_id, allgemein) + SELECT uuid_generate_v4(), fp_baugebiet.id, t.sondernutzung_unnested + FROM fp_baugebiet + CROSS JOIN unnest(fp_baugebiet."sonderNutzung") as t(sondernutzung_unnested); + + create or replace function fp_baugebiet_sync_attr_sondernutzung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'sondernutzung' THEN + DELETE FROM fp_komplexe_sondernutzung WHERE baugebiet_id = NEW.id; + + INSERT INTO fp_komplexe_sondernutzung (id, baugebiet_id, allgemein) + SELECT uuid_generate_v4(), fp_baugebiet.id, t.sondernutzung_unnested + FROM fp_baugebiet + CROSS JOIN unnest(fp_baugebiet."sonderNutzung") as t(sondernutzung_unnested); + ELSE + -- make sure no duplicates are inserted by selecting distinct from unnested array + UPDATE fp_baugebiet SET "sonderNutzung" = ARRAY(SELECT DISTINCT UNNEST("sonderNutzung" || ARRAY[NEW.allgemein])) + WHERE id = NEW.baugebiet_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger fp_baugebiet_sync_attr_sondernutzung + after insert or update of "sonderNutzung" on fp_baugebiet + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_baugebiet_sync_attr_sondernutzung('sondernutzung'); + + create constraint trigger fp_baugebiet_sync_attr_sondernutzung + after insert or update of allgemein on fp_komplexe_sondernutzung + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_baugebiet_sync_attr_sondernutzung('baugebiet_id'); + + create or replace function fp_baugebiet_sync_attr_sondernutzung_on_delete() + returns trigger language plpgsql as $$ + begin + -- make sure no duplicates are inserted by selecting distinct from unnested array + UPDATE fp_baugebiet SET "sonderNutzung" = array_remove("sonderNutzung", OLD.allgemein) + WHERE id = OLD.baugebiet_id; + RETURN NULL; + end $$; + + create constraint trigger fp_baugebiet_sync_attr_sondernutzung_on_delete + after delete on fp_komplexe_sondernutzung + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_baugebiet_sync_attr_sondernutzung_on_delete(); + """) + + # CR-025 FP_Gemeindedarf + op.execute(""" + CREATE TABLE fp_zweckbestimmung_gemeinbedarf ( + id UUID NOT NULL, + allgemein xp_zweckbestimmunggemeinbedarf, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + gemeinbedarf_id UUID, + FOREIGN KEY(gemeinbedarf_id) REFERENCES fp_gemeinbedarf (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + ALTER TABLE fp_gemeinbedarf ADD COLUMN traeger xp_traegerschaft; + ALTER TABLE fp_gemeinbedarf ADD COLUMN "zugunstenVon" VARCHAR; + + INSERT INTO fp_zweckbestimmung_gemeinbedarf (id, gemeinbedarf_id, allgemein) + SELECT uuid_generate_v4(), fp_gemeinbedarf.id, t.zweckbestimmung_unnested + FROM fp_gemeinbedarf + CROSS JOIN unnest(fp_gemeinbedarf."zweckbestimmung") as t(zweckbestimmung_unnested); + + create or replace function fp_gemeinbedarf_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmung' THEN + DELETE FROM fp_zweckbestimmung_gemeinbedarf WHERE gemeinbedarf_id = NEW.id; + + INSERT INTO fp_zweckbestimmung_gemeinbedarf (id, gemeinbedarf_id, allgemein) + SELECT uuid_generate_v4(), fp_gemeinbedarf.id, t.zweckbestimmung_unnested + FROM fp_gemeinbedarf + CROSS JOIN unnest(fp_gemeinbedarf."zweckbestimmung") as t(zweckbestimmung_unnested); + ELSE + UPDATE fp_gemeinbedarf SET zweckbestimmung = array_remove(zweckbestimmung, OLD.allgemein) WHERE id = OLD.gemeinbedarf_id; + + -- make sure no duplicates are inserted by selecting distinct from unnested array + UPDATE fp_gemeinbedarf SET zweckbestimmung = ARRAY(SELECT DISTINCT UNNEST(zweckbestimmung || ARRAY[NEW.allgemein])) + WHERE id = NEW.gemeinbedarf_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger fp_gemeinbedarf_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on fp_gemeinbedarf + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_gemeinbedarf_sync_attr_zweckbestimmung('zweckbestimmung'); + + create constraint trigger fp_gemeinbedarf_sync_attr_zweckbestimmung + after insert or update of allgemein on fp_zweckbestimmung_gemeinbedarf + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_gemeinbedarf_sync_attr_zweckbestimmung('gemeinbedarf_id'); + + create or replace function fp_gemeinbedarf_sync_attr_zweckbestimmung_on_delete() + returns trigger language plpgsql as $$ + begin + UPDATE fp_gemeinbedarf SET zweckbestimmung = array_remove(zweckbestimmung, OLD.allgemein) + WHERE id = OLD.gemeinbedarf_id; + RETURN NULL; + end $$; + + create constraint trigger fp_gemeinbedarf_sync_attr_zweckbestimmung_on_delete + after delete on fp_zweckbestimmung_gemeinbedarf + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_gemeinbedarf_sync_attr_zweckbestimmung_on_delete(); + """) + + # CR-025 FP_SpielSportanlage + op.execute(""" + CREATE TABLE fp_zweckbestimmung_sport ( + id UUID NOT NULL, + allgemein xp_zweckbestimmungspielsportanlage, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + sportanlage_id UUID, + FOREIGN KEY(sportanlage_id) REFERENCES fp_spiel_sportanlage (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + ALTER TABLE fp_spiel_sportanlage ADD COLUMN "zugunstenVon" VARCHAR; + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM fp_spiel_sportanlage g + ) + INSERT INTO fp_zweckbestimmung_sport (id, allgemein, sportanlage_id) + SELECT * FROM upd; + + create or replace function fp_sport_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE fp_zweckbestimmung_sport + SET allgemein = NEW.zweckbestimmung + WHERE sportanlage_id = NEW.id; + ELSE + UPDATE fp_spiel_sportanlage SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.sportanlage_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger fp_sport_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on fp_spiel_sportanlage + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_sport_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger fp_sport_sync_attr_zweckbestimmung + after insert or update of allgemein on fp_zweckbestimmung_sport + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_sport_sync_attr_zweckbestimmung('sportanlage_id'); + """) + + # CR-025 FP_Gruen + op.execute(""" + CREATE TABLE fp_zweckbestimmung_gruen ( + id UUID NOT NULL, + allgemein xp_zweckbestimmunggruen, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + gruenflaeche_id UUID, + FOREIGN KEY(gruenflaeche_id) REFERENCES fp_gruen (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + ALTER TABLE fp_gruen ADD COLUMN "zugunstenVon" VARCHAR; + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM fp_gruen g + ) + INSERT INTO fp_zweckbestimmung_gruen (id, allgemein, gruenflaeche_id) + SELECT * FROM upd; + + create or replace function fp_gruen_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE fp_zweckbestimmung_gruen + SET allgemein = NEW.zweckbestimmung + WHERE gruenflaeche_id = NEW.id; + ELSE + UPDATE fp_gruen SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.gruenflaeche_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger fp_gruen_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on fp_gruen + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_gruen_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger fp_gruen_sync_attr_zweckbestimmung + after insert or update of allgemein on fp_zweckbestimmung_gruen + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_gruen_sync_attr_zweckbestimmung('gruenflaeche_id'); + """) + + # CR-035 FP_Landwirtschaft + op.execute(""" + CREATE TABLE fp_zweckbestimmung_landwirtschaft ( + id UUID NOT NULL, + allgemein xp_zweckbestimmunglandwirtschaft, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + landwirtschaft_id UUID, + FOREIGN KEY(landwirtschaft_id) REFERENCES fp_landwirtschaft (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM fp_landwirtschaft g + ) + INSERT INTO fp_zweckbestimmung_landwirtschaft (id, allgemein, landwirtschaft_id) + SELECT * FROM upd; + + create or replace function fp_landwirtschaft_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE fp_zweckbestimmung_landwirtschaft + SET allgemein = NEW.zweckbestimmung + WHERE landwirtschaft_id = NEW.id; + ELSE + UPDATE fp_landwirtschaft SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.landwirtschaft_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger fp_landwirtschaft_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on fp_landwirtschaft + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_landwirtschaft_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger fp_landwirtschaft_sync_attr_zweckbestimmung + after insert or update of allgemein on fp_zweckbestimmung_landwirtschaft + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_landwirtschaft_sync_attr_zweckbestimmung('landwirtschaft_id'); + """) + + # CR-035 FP_WaldFlaeche + op.execute(""" + CREATE TABLE fp_zweckbestimmung_wald ( + id UUID NOT NULL, + allgemein xp_zweckbestimmungwald, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + waldflaeche_id UUID, + FOREIGN KEY(waldflaeche_id) REFERENCES fp_wald (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM fp_wald g + ) + INSERT INTO fp_zweckbestimmung_wald (id, allgemein, waldflaeche_id) + SELECT * FROM upd; + + create or replace function fp_wald_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE fp_zweckbestimmung_wald + SET allgemein = NEW.zweckbestimmung + WHERE waldflaeche_id = NEW.id; + ELSE + UPDATE fp_wald SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.waldflaeche_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger fp_wald_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on fp_wald + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_wald_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger fp_wald_sync_attr_zweckbestimmung + after insert or update of allgemein on fp_zweckbestimmung_wald + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_wald_sync_attr_zweckbestimmung('waldflaeche_id'); + """) + + # Neues LP-Modell + op.execute(""" + ALTER TABLE xp_plan_gemeinde ADD COLUMN lp_plan_id uuid REFERENCES lp_plan(id) ON DELETE CASCADE; + ALTER TABLE lp_plan ADD COLUMN plangeber_id uuid REFERENCES xp_plangeber(id); + + ALTER TABLE lp_plan ADD COLUMN "auslegungsStartDatum" DATE[]; + ALTER TABLE lp_plan ADD COLUMN "auslegungsEndDatum" DATE[]; + ALTER TABLE lp_plan ADD COLUMN "tOeBbeteiligungsStartDatum" DATE[]; + ALTER TABLE lp_plan ADD COLUMN "tOeBbeteiligungsEndDatum" DATE[]; + ALTER TABLE lp_plan ADD COLUMN "oeffentlichkeitsBetStartDatum" DATE[]; + ALTER TABLE lp_plan ADD COLUMN "oeffentlichkeitsBetEndDatum" DATE[]; + + ALTER TABLE lp_plan ADD COLUMN "veroeffentlichungsDatum" DATE; + + ALTER TABLE lp_plan ADD COLUMN "sonstVerfahrensText" VARCHAR; + ALTER TABLE lp_plan ADD COLUMN "startBedingungen" VARCHAR; + ALTER TABLE lp_plan ADD COLUMN "endeBedingungen" VARCHAR; + """) + + +def downgrade(): + op.execute(""" + ALTER TABLE xp_plan_gemeinde DROP COLUMN lp_plan_id; + ALTER TABLE lp_plan DROP COLUMN plangeber_id; + + ALTER TABLE lp_plan DROP COLUMN "auslegungsStartDatum"; + ALTER TABLE lp_plan DROP COLUMN "auslegungsEndDatum"; + ALTER TABLE lp_plan DROP COLUMN "tOeBbeteiligungsStartDatum"; + ALTER TABLE lp_plan DROP COLUMN "tOeBbeteiligungsEndDatum"; + ALTER TABLE lp_plan DROP COLUMN "oeffentlichkeitsBetStartDatum"; + ALTER TABLE lp_plan DROP COLUMN "oeffentlichkeitsBetEndDatum"; + ALTER TABLE lp_plan DROP COLUMN "veroeffentlichungsDatum"; + ALTER TABLE lp_plan DROP COLUMN "sonstVerfahrensText"; + ALTER TABLE lp_plan DROP COLUMN "startBedingungen"; + ALTER TABLE lp_plan DROP COLUMN "endeBedingungen"; + """) + + op.execute("DROP TRIGGER fp_wald_sync_attr_zweckbestimmung ON fp_wald") + op.execute("DROP TABLE fp_zweckbestimmung_wald") + + op.execute("DROP TRIGGER fp_landwirtschaft_sync_attr_zweckbestimmung ON fp_landwirtschaft") + op.execute("DROP TABLE fp_zweckbestimmung_landwirtschaft") + + op.execute("DROP TRIGGER fp_gruen_sync_attr_zweckbestimmung ON fp_gruen") + op.execute("DROP TABLE fp_zweckbestimmung_gruen") + op.execute('ALTER TABLE fp_gruen DROP COLUMN "zugunstenVon"') + + op.execute("DROP TRIGGER fp_sport_sync_attr_zweckbestimmung ON fp_spiel_sportanlage") + op.execute("DROP TABLE fp_zweckbestimmung_gemeinbedarf") + op.execute('ALTER TABLE fp_spiel_sportanlage DROP COLUMN "zugunstenVon"') + + op.execute("DROP TRIGGER fp_gemeinbedarf_sync_attr_zweckbestimmung_on_delete ON fp_zweckbestimmung_gemeinbedarf") + op.execute("DROP TRIGGER fp_gemeinbedarf_sync_attr_zweckbestimmung ON fp_zweckbestimmung_gemeinbedarf") + op.execute("DROP TRIGGER fp_gemeinbedarf_sync_attr_zweckbestimmung ON fp_gemeinbedarf") + op.execute("DROP TABLE fp_zweckbestimmung_gemeinbedarf") + op.execute(""" + ALTER TABLE fp_gemeinbedarf DROP COLUMN "zugunstenVon"; + ALTER TABLE fp_gemeinbedarf DROP COLUMN traeger; + """) + + op.execute("DROP TRIGGER fp_baugebiet_sync_attr_sondernutzung_on_delete ON fp_komplexe_sondernutzung") + op.execute("DROP TRIGGER fp_baugebiet_sync_attr_sondernutzung ON fp_komplexe_sondernutzung") + op.execute("DROP TRIGGER fp_baugebiet_sync_attr_sondernutzung ON fp_baugebiet") + op.execute("DROP TABLE fp_komplexe_sondernutzung") + + op.execute(""" + ALTER TABLE fp_baugebiet DROP COLUMN "GFZdurchschnittlich"; + ALTER TABLE fp_baugebiet DROP COLUMN "abweichungBauNVO"; + """) + + op.execute(""" + ALTER TABLE fp_plan DROP COLUMN "versionBauNVO_id"; + ALTER TABLE fp_plan DROP COLUMN "versionBauGB_id"; + ALTER TABLE fp_plan DROP COLUMN "versionSonstRechtsgrundlage_id"; + """) + + op.execute("DROP TABLE so_wasserwirtschaft") + op.execute("DROP TYPE so_klassifizwasserwirtschaft") + + op.execute("DROP TABLE so_gewaesser") + op.execute("DROP TABLE so_festlegung_gewaesser") + op.execute("DROP TYPE so_festlegung_gewaesser") + + op.execute("ALTER TABLE xp_externe_referenz DROP COLUMN veraenderungssperre_id") + op.execute("DROP TABLE bp_veraenderungssperre_daten") + op.execute("DROP TYPE bp_verlaengerungveraenderungssperre") + + op.execute("DROP TABLE bp_zweckbestimmung_versorgung") + op.execute("DROP TRIGGER bp_versorgung_sync_attr_zweckbestimmung ON bp_versorgung") + + op.execute('ALTER TABLE bp_pflanzung DROP COLUMN "pflanzenArt"') + + op.execute("DROP TABLE bp_zweckbestimmung_wald") + op.execute("DROP TRIGGER bp_wald_sync_attr_zweckbestimmung ON bp_wald") + + op.execute("DROP TABLE bp_zweckbestimmung_landwirtschaft") + op.execute("DROP TRIGGER bp_landwirtschaft_sync_attr_zweckbestimmung ON bp_landwirtschaft") + + op.execute("DROP TABLE bp_zweckbestimmung_gemeinbedarf") + op.execute("DROP TRIGGER bp_gemeinbedarf_sync_attr_zweckbestimmung ON bp_gemeinbedarf") + + op.execute("DROP TABLE bp_zweckbestimmung_sport") + op.execute("DROP TRIGGER bp_spiel_sportanlage_sync_attr_zweckbestimmung ON bp_spiel_sportanlage") + + # change FP_Gemeinbedarf.zweckbestimmung back to single enum + op.execute("ALTER TABLE fp_gemeinbedarf ALTER zweckbestimmung DROP DEFAULT, ALTER zweckbestimmung type xp_zweckbestimmunggemeinbedarf using zweckbestimmung[1], alter zweckbestimmung set default NULL;") + + op.execute("DROP TABLE so_strassenverkehr") + op.execute("DROP TABLE so_zweckbestimmung_strassenverkehr") + op.execute("DROP TYPE so_zweckbestimmungstrassenverkehr") + op.execute("DROP TYPE so_strasseneinteilung") + + # change gml:AngleType column nordwinkel back to int + op.execute('ALTER TABLE so_objekt ALTER COLUMN nordwinkel TYPE integer USING nordwinkel::integer;') + + op.execute("DROP TRIGGER bp_baugebiet_sync_attr_sondernutzung_on_delete ON bp_komplexe_sondernutzung") + op.execute("DROP TRIGGER bp_baugebiet_sync_attr_sondernutzung ON bp_komplexe_sondernutzung") + op.execute("DROP TRIGGER bp_baugebiet_sync_attr_sondernutzung ON bp_baugebiet") + op.execute("DROP TABLE bp_komplexe_sondernutzung") + + op.execute("DROP TABLE bp_zweckbestimmung_gruen") + op.execute("DROP TRIGGER bp_gruenflaeche_sync_attr_zweckbestimmung ON bp_gruenflaeche") + + op.execute("ALTER TABLE xp_objekt DROP COLUMN rechtscharakter") + op.execute("DROP TRIGGER so_objekt_sync_attr_rechtscharakter ON xp_objekt") + op.execute("DROP TRIGGER fp_objekt_sync_attr_rechtscharakter ON xp_objekt") + op.execute("DROP TRIGGER bp_objekt_sync_attr_rechtscharakter ON xp_objekt") + op.execute("DROP TRIGGER xp_objekt_sync_attr_rechtscharakter ON bp_objekt") + op.execute("DROP TRIGGER xp_objekt_sync_attr_rechtscharakter_from_fp ON fp_objekt") + op.execute("DROP TRIGGER xp_objekt_sync_attr_rechtscharakter_from_so ON so_objekt") + + op.execute("ALTER TABLE bp_bereich DROP COLUMN verfahren") + op.execute("DROP TRIGGER bp_plan_sync_attr_verfahren ON bp_bereich") + op.execute("DROP TRIGGER bp_bereich_sync_attr_verfahren ON bp_plan") + + op.execute('UPDATE xp_spe_daten SET "klassifizMassnahme" = \'ArtentreicherGehoelzbestand\'::xp_spemassnahmentypen WHERE "klassifizMassnahme" = \'ArtenreicherGehoelzbestand\'::xp_spemassnahmentypen;') + + op.execute("ALTER TABLE bp_gemeinbedarf DROP COLUMN traeger") + op.execute("DROP TYPE xp_traegerschaft CASCADE") + + op.execute("ALTER TABLE xp_plan DROP COLUMN hoehenbezug") + op.execute("DROP TRIGGER xp_plan_sync_attr_hoehenbezug ON bp_plan") + op.execute("DROP TRIGGER bp_plan_sync_attr_hoehenbezug ON xp_plan") + + op.drop_column('xp_objekt', 'gesetzlicheGrundlage_id') + op.drop_table('xp_gesetzliche_grundlage') + op.execute("CREATE TYPE xp_gesetzliche_grundlage AS ENUM ()") + op.add_column('xp_objekt', sa.Column('gesetzlicheGrundlage', sa.Enum('', name='xp_gesetzliche_grundlage'), nullable=True)) diff --git a/build_env/Dockerfile b/build_env/Dockerfile new file mode 100644 index 0000000..678f490 --- /dev/null +++ b/build_env/Dockerfile @@ -0,0 +1,13 @@ +FROM qgis/qgis:latest + +LABEL Description="Docker Image with SAGis XPlanung dependencies" + +WORKDIR /app +ENV DISPLAY=:99 + +COPY . . + +RUN python3 -m pip install -r requirements.txt +RUN python3 -m pip install pytest pytest-cov pytest-qt pytest-mock pytest-asyncio nest-asyncio alembic + +ENTRYPOINT ["sh"] \ No newline at end of file diff --git a/build_env/qgis_install.ps1 b/build_env/qgis_install.ps1 new file mode 100644 index 0000000..19764c2 --- /dev/null +++ b/build_env/qgis_install.ps1 @@ -0,0 +1,50 @@ +#Requires -RunAsAdministrator + +<# +.Synopsis + Download the OSGeo4W installer then download and install QGIS LTR (through the 'full' meta-package). +.DESCRIPTION + This script will: + 1. change the current directory to the user downloads folder + 2. download the OSGeo4W installer + 3. launch it passing command-line parameters to DOWNLOAD packages required to QGIS LTR FULL + 4. launch it passing command-line parameters to INSTALL QGIS LTR + Documentation reference: https://trac.osgeo.org/osgeo4w/wiki/CommandLine +#> + +# Save current working directory +$starter_path = Get-Location + +# Move into the user download directory +Set-Location -Path "$env:USERPROFILE/Downloads" + +# Download installer if not exists +if (-Not (Test-Path "osgeo4w_v2-setup.exe" -PathType leaf )) { + Write-Host "= Start downloading the OSGeo4W installer" -ForegroundColor Yellow + Invoke-WebRequest -Uri "https://download.osgeo.org/osgeo4w/v2/osgeo4w-setup.exe" -OutFile "osgeo4w_v2-setup.exe" + Write-Host "== Installer downloaded into $env:USERPROFILE/Downloads" -ForegroundColor Yellow +} +else +{ Write-Host "= OSGeo4W installer already exists. Let's use it!" -ForegroundColor Blue } + +Download and install (same command to upgrade with clean up) +Write-Host "=== Start installing / upgrading QGIS LTR..." -ForegroundColor Yellow +& .\osgeo4w_v2-setup.exe ` + --advanced ` + --arch x86_64 ` + --autoaccept ` + --delete-orphans ` + --local-package-dir "$env:APPDATA/OSGeo4W_v2-Packages" ` + --menu-name "QGIS LTR" ` + --no-desktop ` + --packages qgis-ltr-full ` + --quiet-mode ` + --root "$env:ProgramFiles/OSGeo4W_v2" ` + --site "http://www.norbit.de/osgeo4w/v2" ` + --site "http://download.osgeo.org/osgeo4w/v2" ` + --site "http://ftp.osuosl.org/pub/osgeo/download/osgeo4w/v2" ` + --upgrade-also ` + +# Return to the initial directory +Set-Location -Path $starter_path +Write-Host "==== Work is done!" -ForegroundColor Green \ No newline at end of file diff --git a/docs/BPlan001_5-3.png b/docs/BPlan001_5-3.png new file mode 100644 index 0000000..d5ef601 Binary files /dev/null and b/docs/BPlan001_5-3.png differ diff --git a/docs/UPDATE.md b/docs/UPDATE.md new file mode 100644 index 0000000..8a7e307 --- /dev/null +++ b/docs/UPDATE.md @@ -0,0 +1,9 @@ +## Anweisungen zum Update + +0. Zur Sicherheit zunächst ein Backup der existierenden Datenbank durchführen. +1. Im QGIS-Erweiterungsmanager SAGis XPlanung suchen und `Erweiterung deinstallieren` wählen. Im Anschluss QGIS schließen. +2. Verbindung zur Datenbank herstellen bspw. in pgAdmin und das SQL-Skript zur Migration ausführen. +3. Nach erfolgreichem Update der Datenbank das Installationsprogramm ausführen. +> [!NOTE] +> Darauf achten, dass der Installationspfad im QGIS-Plugin-Verzeichnis mit `../SAGisXPlanung` endet. +> (Nicht nur `../XPlanung`, wie es in früheren Versionen der Fall war) diff --git a/docs/toc.png b/docs/toc.png new file mode 100644 index 0000000..f785c5c Binary files /dev/null and b/docs/toc.png differ diff --git a/installer/sagis_icon.bmp b/installer/sagis_icon.bmp new file mode 100644 index 0000000..aa31ed7 Binary files /dev/null and b/installer/sagis_icon.bmp differ diff --git a/installer/sagis_icon.ico b/installer/sagis_icon.ico new file mode 100644 index 0000000..a076ac5 Binary files /dev/null and b/installer/sagis_icon.ico differ diff --git a/installer/setup.iss b/installer/setup.iss new file mode 100644 index 0000000..4a3d720 --- /dev/null +++ b/installer/setup.iss @@ -0,0 +1,121 @@ +#define MyAppName "SAGis XPlanung" +#define MyAppPublisher "NTI Deutschland GmbH" +#define MyAppURL "https://www.nti.biz/de/produkte/sagis-loesungen/" + +#ifndef SAGIS_VERSION + #define SAGIS_VERSION "2.2.0-dev" +#endif + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{48626255-FF2B-4145-BDB7-B1A76C2F0D46} +AppName={#MyAppName} +AppVersion={#SAGIS_VERSION} +;AppVerName={#MyAppName} {#SAGIS_VERSION} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={userappdata}\QGIS\QGIS3\profiles\default\python\plugins\SAGisXPlanung +#ifndef WITH_CIVIL_IMPORT + #define WITH_CIVIL_IMPORT False + OutputBaseFilename={#MyAppName}_{#SAGIS_VERSION} +#else + #define WITH_CIVIL_IMPORT True + OutputBaseFilename={#MyAppName}_{#SAGIS_VERSION}_c3d +#endif + +DefaultGroupName={#MyAppName} +DisableProgramGroupPage=yes +DisableDirPage=no +DisableWelcomePage=no +; Remove the following line to run in administrative install mode (install for all users.) +PrivilegesRequired=lowest +Compression=lzma +SolidCompression=yes +WizardStyle=modern +WizardSmallImageFile=sagis_icon.bmp +SetupIconFile=sagis_icon.ico + +[Languages] +Name: "german"; MessagesFile: "compiler:Languages\German.isl" + +[Files] +Source: "SAGisXPlanung\*"; DestDir: "{app}"; Check: CheckWithCivilImport; Flags: recursesubdirs +Source: "dependencies\*"; DestDir: "{app}\dependencies"; Flags: recursesubdirs +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +; removes potential "XPlanung" folder from installations of older versions +; removes python dependency files after installation +[InstallDelete] +Type: filesandordirs; Name: "{userappdata}\QGIS\QGIS3\profiles\default\python\plugins\XPlanung" +Type: filesandordirs; Name: "{userappdata}\QGIS\QGIS3\profiles\default\python\plugins\SAGisXPlanung\dependencies" + +[Run] +Filename: "cmd.exe"; Parameters: "/C ""OSGeo4W.bat o4w_env & python3 -m ensurepip --upgrade"""; WorkingDir: "{code:GetDir|0}"; StatusMsg: Python Paketmanager (pip) installieren...; Flags: runhidden waituntilterminated; BeforeInstall: SetMarqueeProgress(True) +Filename: "cmd.exe"; Parameters: "/C ""OSGeo4W.bat o4w_env & for %x in ({app}\dependencies\*.whl) do python3 -m pip install %x"""; WorkingDir: "{code:GetDir|0}"; StatusMsg: Python Abhängigkeiten installieren...; Flags: runhidden waituntilterminated; AfterInstall: SetMarqueeProgress(False) +Filename: "cmd.exe"; Parameters: "/C ""OSGeo4W.bat o4w_env & (qgis || qgis-ltr)"""; WorkingDir: "{code:GetDir|0}"; Description: QGIS starten; Flags: runhidden nowait postinstall skipifsilent + +[Code] +var + Page: TInputDirWizardPage; + FilePath: string; + +procedure SetMarqueeProgress(Marquee: Boolean); +begin + if Marquee then + begin + WizardForm.ProgressGauge.Style := npbstMarquee; + end + else + begin + WizardForm.ProgressGauge.Style := npbstNormal; + end; +end; + +function GetDir(Param: string): string; +begin + Result := Page.Values[StrToInt(Param)]; +end; + +procedure InitializeWizard; +begin + Page := CreateInputDirPage(wpWelcome, + 'QGIS Installationsordner wählen', 'Bitte wählen sie den Installationsordner von QGIS auf diesem Rechner', + 'Der Installer benötigt diese Angabe um SAGis XPlanung korrekt zu installieren.' + #13#10 + + 'Typische Orte der Installation sind C:\OSGeo4W\ oder C:\Program Files\QGIS 3.*', + False, 'New Folder'); + + Page.Add('QGIS Ordner'); + + Page.Values[0] := ('C:\OSGeo4W'); +end; + +function NextButtonClick(CurPageID: Integer): Boolean; +begin + Result := True; + + if CurPageID = Page.ID then + begin + FilePath := Page.Values[0] + '\OSGeo4W.bat' + if not FileExists(FilePath) then + begin + MsgBox('Installer konnte Teile der QGIS-Installation nicht finden. Überprüfen Sie den angegebenen Ordner.', mbError, MB_OK); + Result := False; + end; + + end; +end; + +function CheckWithCivilImport: Boolean; +begin + if ({#WITH_CIVIL_IMPORT} = 0) and (ExtractFileName(CurrentFileName) = 'import_civil.py') then + begin + Result := False + end + else + begin + Result := True; + end; +end; diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b5a3c46 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel" +] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9dbdf75 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +GeoAlchemy2==0.12.5 +lxml==4.6.3 +shapely==2.0.0 --no-binary shapely +SQLAlchemy==1.4.13 +qasync==0.22.0 +asyncpg==0.26.0 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..8fe7c76 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,22 @@ +[metadata] +name = SAGis XPlanung +version = 2.2.0 +author = NTI Deutschland GmbH +author_email = qgis-de@nti.biz +description = QGIS-Plugin zur XPlanung-konformen Erfassung und Verwaltung von Bauleitplänen +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/nti-de/SAGisXPlanung/ +project_urls = +classifiers = + Programming Language :: Python :: 3 + Operating System :: OS Independent + +[options] +package_dir = + = src +packages = find: +python_requires = >=3.6 + +[options.packages.find] +where = src \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..57e4462 --- /dev/null +++ b/setup.py @@ -0,0 +1,51 @@ +import os + +from setuptools import setup, find_packages + +from Cython.Build import cythonize + +from distutils.command import build_ext + + +# random bug fix: copied from https://bugs.python.org/issue35893#msg342821 +def get_export_symbols(self, ext): + parts = ext.name.split(".") + if parts[-1] == "__init__": + initfunc_name = "PyInit_" + parts[-2] + else: + initfunc_name = "PyInit_" + parts[-1] + +build_ext.build_ext.get_export_symbols = get_export_symbols + + +EXCLUDE_FILES = [ + 'src/SAGisXPlanung/__init__.py' +] + + +def get_ext_paths(root_dir, exclude_files): + """get filepaths for compilation""" + paths = [] + print(root_dir) + for root, dirs, files in os.walk(root_dir): + for filename in files: + if os.path.splitext(filename)[1] != '.py': + continue + + file_path = os.path.join(root, filename) + if file_path in exclude_files: + continue + + paths.append(file_path) + return paths + + +setup( + name='SAGisXPlanung', + version='2.2.0', + packages=find_packages(), + ext_modules=cythonize( + get_ext_paths('src/SAGisXPlanung/', EXCLUDE_FILES), + compiler_directives={'language_level': 3} + ) +) \ No newline at end of file diff --git a/src/SAGisXPlanung/BPlan/BP_Basisobjekte/__init__.py b/src/SAGisXPlanung/BPlan/BP_Basisobjekte/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/BPlan/BP_Basisobjekte/data_types.py b/src/SAGisXPlanung/BPlan/BP_Basisobjekte/data_types.py new file mode 100644 index 0000000..d16a32f --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Basisobjekte/data_types.py @@ -0,0 +1,33 @@ +from uuid import uuid4 + +from sqlalchemy import Column, Date, Enum, String, ForeignKey +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from SAGisXPlanung import Base +from SAGisXPlanung.BPlan.BP_Basisobjekte.enums import BP_VerlaengerungVeraenderungssperre +from SAGisXPlanung.XPlan.mixins import RelationshipMixin, ElementOrderMixin + + +class BP_VeraenderungssperreDaten(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Daten für eine Veränderungssperre. """ + + __tablename__ = 'bp_veraenderungssperre_daten' + __avoidRelation__ = ['plan'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + startDatum = Column(Date, nullable=False) + endDatum = Column(Date, nullable=False) + verlaengerung = Column(Enum(BP_VerlaengerungVeraenderungssperre), nullable=False) + beschlussDatum = Column(Date) + + refBeschluss = relationship("XP_ExterneReferenz", back_populates="veraenderungssperre", + cascade="all, delete", passive_deletes=True, uselist=False) + + plan_id = Column(UUID(as_uuid=True), ForeignKey('bp_plan.id', ondelete='CASCADE')) + plan = relationship("BP_Plan", back_populates="rel_veraenderungssperre") + + @classmethod + def avoid_export(cls): + return ['plan_id'] diff --git a/src/SAGisXPlanung/BPlan/BP_Basisobjekte/enums.py b/src/SAGisXPlanung/BPlan/BP_Basisobjekte/enums.py new file mode 100644 index 0000000..ea68a39 --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Basisobjekte/enums.py @@ -0,0 +1,110 @@ +from enum import Enum + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin + + +class BP_PlanArt(XPlanungEnumMixin, Enum): + """ Typ des vorliegenden Bebauungsplans. """ + + def __new__(cls, *args, **kwds): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: int, version: XPlanVersion = None): + self._xplan_version = version + + @property + def version(self) -> XPlanVersion: + return self._xplan_version + + BPlan = 1000 + EinfacherBPlan = 10000 + QualifizierterBPlan = 10001 + BebauungsplanZurWohnraumversorgung = 10002, XPlanVersion.SIX + VorhabenbezogenerBPlan = 3000 + VorhabenUndErschliessungsplan = 3100 + InnenbereichsSatzung = 4000 + KlarstellungsSatzung = 40000 + EntwicklungsSatzung = 40001 + ErgaenzungsSatzung = 40002 + AussenbereichsSatzung = 5000 + OertlicheBauvorschrift = 7000 + Sonstiges = 9999 + + +class BP_Verfahren(XPlanungEnumMixin, Enum): + """ Verfahrensart der BPlan-Aufstellung oder -Änderung. """ + + Normal = 1000 + Parag13 = 2000 + Parag13a = 3000 + Parag13b = 4000 + + +class BP_Rechtsstand(XPlanungEnumMixin, Enum): + """ Aktueller Rechtsstand des Plans. """ + + def __new__(cls, *args, **kwds): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: int, version: XPlanVersion = None): + self._xplan_version = version + + @property + def version(self) -> XPlanVersion: + return self._xplan_version + + Aufstellungsbeschluss = 1000 + Entwurf = 2000 + FruehzeitigeBehoerdenBeteiligung = 2100 + FruehzeitigeOeffentlichkeitsBeteiligung = 2200 + Entwurfsbeschluss = 2250, XPlanVersion.SIX + BehoerdenBeteiligung = 2300 + OeffentlicheAuslegung = 2400 + Satzung = 3000 + InkraftGetreten = 4000 + TeilweiseUntergegangen = 4500 + TeilweiseAufgehoben = 45000, XPlanVersion.SIX + TeilweiseAusserKraft = 45001, XPlanVersion.SIX + Untergegangen = 5000 + Aufgehoben = 50000 + AusserKraft = 50001 + + +class BP_Rechtscharakter(XPlanungEnumMixin, Enum): + """ Rechtliche Charakterisierung des Planinhaltes """ + + Festsetzung = 1000 + NachrichtlicheUebernahme = 2000 + Hinweis = 3000 + Vermerk = 4000 + Kennzeichnung = 5000 + Unbekannt = 9998 + + def to_xp_rechtscharakter(self): + from SAGisXPlanung.XPlan.enums import XP_Rechtscharakter + + if self.value == 1000: + return XP_Rechtscharakter.FestsetzungBPlan + elif self.value == 2000: + return XP_Rechtscharakter.NachrichtlicheUebernahme + elif self.value == 3000: + return XP_Rechtscharakter.Hinweis + elif self.value == 4000: + return XP_Rechtscharakter.Vermerk + elif self.value == 5000: + return XP_Rechtscharakter.Kennzeichnung + elif self.value == 9998: + return XP_Rechtscharakter.Unbekannt + + +class BP_VerlaengerungVeraenderungssperre(XPlanungEnumMixin, Enum): + """ Gibt an, ob die Veränderungssperre bereits ein-oder zweimal verlängert wurde. """ + + Keine = 1000 + ErsteVerlaengerung = 2000 + ZweiteVerlaengerung = 3000 diff --git a/src/SAGisXPlanung/BPlan/BP_Basisobjekte/feature_types.py b/src/SAGisXPlanung/BPlan/BP_Basisobjekte/feature_types.py new file mode 100644 index 0000000..1c5c347 --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Basisobjekte/feature_types.py @@ -0,0 +1,241 @@ +import uuid +from typing import List + +from geoalchemy2 import Geometry, WKBElement, WKTElement +from sqlalchemy import Column, Enum, String, Date, ARRAY, Boolean, ForeignKey, event +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import relationship, column_property, declared_attr + +from qgis.core import (QgsSimpleLineSymbolLayer, QgsSingleSymbolRenderer, QgsSymbol, QgsWkbTypes, QgsGeometry, + QgsCoordinateReferenceSystem, QgsProject) +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import Qt + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.GML.geometry import enforce_wkb_constraints, geometry_from_spatial_element +from SAGisXPlanung.XPlan.conversions import BP_Rechtscharakter_EnumType +from SAGisXPlanung.XPlan.core import XPCol, XPRelationshipProperty +from SAGisXPlanung.XPlan.data_types import XP_PlanXP_GemeindeAssoc +from SAGisXPlanung.XPlan.enums import XP_VerlaengerungVeraenderungssperre +from SAGisXPlanung.XPlan.feature_types import XP_Plan, XP_Bereich, XP_Objekt +from SAGisXPlanung.BPlan.BP_Basisobjekte.enums import BP_Verfahren, BP_Rechtsstand, BP_PlanArt, BP_Rechtscharakter +from SAGisXPlanung.XPlan.types import XPEnum, GeometryType +from SAGisXPlanung.XPlanungItem import XPlanungItem + + +class BP_Plan(XP_Plan): + """ Die Klasse modelliert einen Bebauungsplan. """ + + def __init__(self): + self.auslegungsEndDatum = [] + self.auslegungsStartDatum = [] + self.traegerbeteiligungsStartDatum = [] + self.traegerbeteiligungsEndDatum = [] + + __tablename__ = 'bp_plan' + __requiredRelationships__ = ["gemeinde"] + __mapper_args__ = { + 'polymorphic_identity': 'bp_plan', + } + + id = Column(ForeignKey("xp_plan.id", ondelete='CASCADE'), primary_key=True) + + gemeinde = relationship("XP_Gemeinde", back_populates="bp_plans", secondary=XP_PlanXP_GemeindeAssoc, doc='Gemeinde') + + plangeber_id = Column(UUID(as_uuid=True), ForeignKey('xp_plangeber.id')) + plangeber = relationship("XP_Plangeber", back_populates="bp_plans", doc='Plangeber') + + planArt = Column(Enum(BP_PlanArt), nullable=False, doc='Art des Planwerks') + # sonstPlanArt: BP_SonstPlanArt[0..1] + verfahren = XPCol(Enum(BP_Verfahren), doc='Verfahren', version=XPlanVersion.FIVE_THREE) + rechtsstand = Column(Enum(BP_Rechtsstand), doc='Rechtsstand') + # status: BP_Status[0..1] + hoehenbezug = XPCol(String(), doc='Höhenbezug', version=XPlanVersion.FIVE_THREE) + aenderungenBisDatum = Column(Date(), doc='Änderungen bis') + aufstellungsbeschlussDatum = Column(Date(), doc='Aufstellungsbeschlussdatum') + + veraenderungssperreBeschlussDatum = XPCol(Date(), doc='Beschlussdatum der Veränderungssperre', + version=XPlanVersion.FIVE_THREE) + veraenderungssperreDatum = XPCol(Date(), doc='Beginn der Veränderungssperre', + version=XPlanVersion.FIVE_THREE) + veraenderungssperreEndDatum = XPCol(Date(), doc='Ende der Veränderungssperre', + version=XPlanVersion.FIVE_THREE) + verlaengerungVeraenderungssperre = XPCol(XPEnum(XP_VerlaengerungVeraenderungssperre, include_default=True), + doc='Verlängerung der Veränderungssperre', + version=XPlanVersion.FIVE_THREE) + + rel_veraenderungssperre = relationship("BP_VeraenderungssperreDaten", back_populates="plan", + cascade="all, delete", passive_deletes=True, uselist=False) + + auslegungsStartDatum = Column(ARRAY(Date), doc='Startdatum des Auslegungszeitraums') + auslegungsEndDatum = Column(ARRAY(Date), doc='Enddatum des Auslegungszeitraums') + traegerbeteiligungsStartDatum = Column(ARRAY(Date), doc='Startdatum der Trägerbeteiligung') + traegerbeteiligungsEndDatum = Column(ARRAY(Date), doc='Enddatum der Trägerbeteiligung') + satzungsbeschlussDatum = Column(Date(), doc='Datum des Satzungsbeschlusses') + rechtsverordnungsDatum = Column(Date(), doc='Datum der Rechtsverordnung') + inkrafttretensDatum = Column(Date(), doc='Datum des Inkrafttretens') + ausfertigungsDatum = Column(Date(), doc='Datum der Ausfertigung') + + @declared_attr + def veraenderungssperre(cls): + return XPCol(Boolean, doc='Veränderungssperre?', version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_veraenderungssperre_attr) + + staedtebaulicherVertrag = Column(Boolean, doc='städtebaulicher Vertrag?') + erschliessungsVertrag = Column(Boolean, doc='Erschließungsvertrag?') + durchfuehrungsVertrag = Column(Boolean, doc='Durchführungsvertrag?') + gruenordnungsplan = Column(Boolean, doc='Grünordnungsplan?') + + versionBauNVODatum = XPCol(Date(), doc='Datum der BauNVO', version=XPlanVersion.FIVE_THREE) + versionBauNVOText = XPCol(String(), doc='Textl. Spezifikation der BauNVO', version=XPlanVersion.FIVE_THREE) + versionBauGBDatum = XPCol(Date(), doc='Datum des BauGB', version=XPlanVersion.FIVE_THREE) + versionBauGBText = XPCol(String(), doc='Textl. Spezifikation des BauGB', version=XPlanVersion.FIVE_THREE) + versionSonstRechtsgrundlageDatum = XPCol(Date(), doc='Datum sonst. Rechtsgrundlage', version=XPlanVersion.FIVE_THREE) + versionSonstRechtsgrundlageText = XPCol(String(), doc='Textl. Spezifikation sonst. Rechtsgrundlage', version=XPlanVersion.FIVE_THREE) + + versionBauNVO_id = XPCol(UUID(as_uuid=True), ForeignKey('xp_gesetzliche_grundlage.id'), + version=XPlanVersion.SIX, attribute='versionBauNVO') + versionBauNVO = relationship("XP_GesetzlicheGrundlage", back_populates="bp_bau_nvo", + foreign_keys=[versionBauNVO_id]) + versionBauGB_id = XPCol(UUID(as_uuid=True), ForeignKey('xp_gesetzliche_grundlage.id'), + version=XPlanVersion.SIX, attribute='versionBauGB') + versionBauGB = relationship("XP_GesetzlicheGrundlage", back_populates="bp_bau_gb", foreign_keys=[versionBauGB_id]) + versionSonstRechtsgrundlage_id = XPCol(UUID(as_uuid=True), ForeignKey('xp_gesetzliche_grundlage.id'), + version=XPlanVersion.SIX, attribute='versionSonstRechtsgrundlage') + versionSonstRechtsgrundlage = relationship("XP_GesetzlicheGrundlage", back_populates="bp_bau_sonst", + foreign_keys=[versionSonstRechtsgrundlage_id]) + + bereich = relationship("BP_Bereich", back_populates="gehoertZuPlan", cascade="all, delete", doc='Bereich') + + @classmethod + def avoid_export(cls): + return ['plangeber_id', 'versionBauNVO_id', 'versionBauGB_id', 'versionSonstRechtsgrundlage_id'] + + @classmethod + def import_veraenderungssperre_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'veraenderungssperre' + else: + return 'rel_veraenderungssperre' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_veraenderungssperre', xplan_attribute='veraenderungssperre', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.8) + line.setPenStyle(Qt.DashDotLine) + + symbol.appendSymbolLayer(line) + return QgsSingleSymbolRenderer(symbol) + + def enforceFlaechenschluss(self): + from SAGisXPlanung.BPlan.BP_Sonstiges.feature_types import BP_FlaecheOhneFestsetzung + + geltungsbereich_geom = self.geometry() + results = [] + + for bereich in self.bereich: # type: BP_Bereich + # 1. build union of all flaechenschluss objects (without BP_FlaecheOhneFestsetzung) + flaechenschluss_geoms = [p.geometry() for p in bereich.planinhalt + if p.flaechenschluss and not isinstance(p, BP_FlaecheOhneFestsetzung)] + combined = QgsGeometry.unaryUnion(flaechenschluss_geoms) + + # fill difference of 1. and Geltungsbereich with BP_FlaecheOhneFestsetzung + diff = geltungsbereich_geom.difference(combined) + + areas_without_usage = [p for p in bereich.planinhalt if p.type == 'bp_flaeche_ohne_festsetzung'] + if areas_without_usage: + fl = areas_without_usage[0] + fl.setGeometry(diff, srid=QgsProject.instance().crs().postgisSrid()) + else: + fl = BP_FlaecheOhneFestsetzung() + fl.id = uuid.uuid4() + fl.flaechenschluss = True + fl.rechtscharakter = BP_Rechtscharakter.Unbekannt + fl.setGeometry(diff, srid=QgsProject.instance().crs().postgisSrid()) + bereich.planinhalt.append(fl) + + results.append(XPlanungItem(xtype=fl.__class__, xid=str(fl.id), parent_xid=str(bereich.id))) + + return results + + +@event.listens_for(BP_Plan, 'before_insert') +@event.listens_for(BP_Plan, 'before_update') +def checkIntegrity(mapper, connection, xp_plan): + if not xp_plan.gemeinde: + raise IntegrityError("Planwerk benötigt mindestens eine Gemeinde", None, xp_plan) + if not xp_plan.raeumlicherGeltungsbereich: + raise IntegrityError('Planwerk benötigt mindestens einen Geltungsbereich!', None, xp_plan) + + +class BP_Bereich(XP_Bereich): + """ Diese Klasse modelliert einen Bereich eines Bebauungsplans, z.B. einen räumlichen oder sachlichen + Teilbereich. """ + + __tablename__ = 'bp_bereich' + __mapper_args__ = { + 'polymorphic_identity': 'bp_bereich', + } + + id = Column(ForeignKey("xp_bereich.id", ondelete='CASCADE'), primary_key=True) + + verfahren = XPCol(Enum(BP_Verfahren), doc='Verfahren', version=XPlanVersion.SIX) + + versionBauGBDatum = XPCol(Date(), doc='Datum des BauGB', version=XPlanVersion.FIVE_THREE) + versionBauGBText = XPCol(String(), doc='Textl. Spezifikation des BauGB', version=XPlanVersion.FIVE_THREE) + versionSonstRechtsgrundlageDatum = XPCol(Date(), doc='Datum sonst. Rechtsgrundlage', + version=XPlanVersion.FIVE_THREE) + versionSonstRechtsgrundlageText = XPCol(String(), doc='Textl. Spezifikation sonst. Rechtsgrundlage', + version=XPlanVersion.FIVE_THREE) + + gehoertZuPlan_id = Column(UUID(as_uuid=True), ForeignKey('bp_plan.id', ondelete='CASCADE')) + gehoertZuPlan = relationship('BP_Plan', back_populates='bereich') + + +class BP_Objekt(XP_Objekt): + """ Basisklasse für alle raumbezogenen Festsetzungen, Hinweise, Vermerke und Kennzeichnungen eines Bebauungsplans """ + + __tablename__ = 'bp_objekt' + __mapper_args__ = { + 'polymorphic_identity': 'bp_objekt', + } + __readonly_columns__ = ['position'] + + id = Column(ForeignKey("xp_objekt.id", ondelete='CASCADE'), primary_key=True) + + rechtscharakter = XPCol(BP_Rechtscharakter_EnumType(BP_Rechtscharakter), nullable=False, doc='Rechtscharakter', + version=XPlanVersion.FIVE_THREE) + + position = Column(Geometry()) + flaechenschluss = Column(Boolean, doc='Flächenschluss') + + def srs(self): + return QgsCoordinateReferenceSystem(f'EPSG:{self.position.srid}') + + def geometry(self): + return geometry_from_spatial_element(self.position) + + def setGeometry(self, geom: QgsGeometry, srid: int = None): + if srid is None and self.position is None: + raise Exception('geometry needs a srid') + self.position = WKTElement(geom.asWkt(), srid=srid or self.position.srid) + + def geomType(self) -> GeometryType: + return self.geometry().type() + + @classmethod + def hidden_inputs(cls): + h = super(BP_Objekt, cls).hidden_inputs() + return h + ['position'] diff --git a/src/SAGisXPlanung/BPlan/BP_Bebauung/__init__.py b/src/SAGisXPlanung/BPlan/BP_Bebauung/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/BPlan/BP_Bebauung/data_types.py b/src/SAGisXPlanung/BPlan/BP_Bebauung/data_types.py new file mode 100644 index 0000000..4d81e4a --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Bebauung/data_types.py @@ -0,0 +1,112 @@ +from typing import List +from uuid import uuid4 + +from lxml import etree +from sqlalchemy import Column, Enum, ForeignKey, String +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from SAGisXPlanung import Base, XPlanVersion +from SAGisXPlanung.BPlan.BP_Bebauung.enums import BP_Dachform +from SAGisXPlanung.XPlan.core import XPCol, XPRelationshipProperty +from SAGisXPlanung.XPlan.enums import XP_Sondernutzungen +from SAGisXPlanung.XPlan.mixins import RelationshipMixin, ElementOrderMixin +from SAGisXPlanung.XPlan.types import Angle, ConformityException + + +class BP_Dachgestaltung(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation einer für die Aufstellung des Plans zuständigen Gemeinde. """ + + __tablename__ = 'bp_dachgestaltung' + __avoidRelation__ = ['baugebiet', 'besondere_nutzung', 'gemeinbedarf'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + DNmin = Column(Angle, doc='Dachneigung, min') + DNmax = Column(Angle, doc='Dachneigung, max') + DN = Column(Angle, doc='Dachneigung') + DNZwingend = Column(Angle, doc='Dachneigung, zwingend') + dachform = Column(Enum(BP_Dachform), doc='Dachform') + + hoehenangabe = relationship("XP_Hoehenangabe", back_populates="dachgestaltung", + cascade="all, delete", passive_deletes=True, uselist=False) + + baugebiet_id = Column(UUID(as_uuid=True), ForeignKey('bp_baugebiet.id', ondelete='CASCADE')) + baugebiet = relationship('BP_BaugebietsTeilFlaeche', back_populates='dachgestaltung') + + besondere_nutzung_id = Column(UUID(as_uuid=True), ForeignKey('bp_besondere_nutzung.id', ondelete='CASCADE')) + besondere_nutzung = relationship('BP_BesondererNutzungszweckFlaeche', back_populates='dachgestaltung') + + gemeinbedarf_id = Column(UUID(as_uuid=True), ForeignKey('bp_gemeinbedarf.id', ondelete='CASCADE')) + gemeinbedarf = relationship('BP_GemeinbedarfsFlaeche', back_populates='dachgestaltung') + + def to_xplan_node(self, node=None, version=XPlanVersion.FIVE_THREE): + from SAGisXPlanung.GML.GMLWriter import GMLWriter + + this_node = etree.SubElement(node, f"{{{node.nsmap['xplan']}}}{self.__class__.__name__}") + GMLWriter.write_attributes(this_node, self, version) + + if self.hoehenangabe is not None and version != XPlanVersion.FIVE_THREE: + sub_node = etree.SubElement(this_node, f"{{{this_node.nsmap['xplan']}}}{'hoehenangabe'}") + self.hoehenangabe.to_xplan_node(sub_node) + return node + + @classmethod + def from_xplan_node(cls, node): + from SAGisXPlanung.GML.GMLReader import GMLReader + + this = GMLReader.read_data_object(node, only_attributes=True) + + hoehenangabe_node = node.find('.//xplan:hoehenangabe', namespaces=node.nsmap) + if hoehenangabe_node is not None: + hoehenangabe = GMLReader.read_data_object(hoehenangabe_node[0]) + setattr(this, 'hoehenangabe', hoehenangabe) + + return this + + @classmethod + def avoid_export(cls): + return ['baugebiet_id', 'besondere_nutzung_id', 'gemeinbedarf_id'] + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='hoehenangabe', xplan_attribute='hoehenangabe', + allowed_version=XPlanVersion.SIX) + ] + + def validate(self): + if self.DNmin and (not self.DNmax and not self.DN and not self.DNZwingend): + return + if self.DNmin and self.DNmax and (not self.DN and not self.DNZwingend): + return + if self.DN and (not self.DNmax and not self.DNmin and not self.DNZwingend): + return + if self.DNZwingend and (not self.DNmax and not self.DNmin and not self.DN): + return + + raise ConformityException('Die Attribute DNmin, DNmax, DN und ' + 'DNZwingend dürfen nur in folgenden Kombinationen ' + 'belegt werden:
  • DNmin
  • DNmin und DNmax
  • DN
  • ' + '
  • DNzwingend
  • ', '4.3.1.1', self.__class__.__name__) + + +class BP_KomplexeSondernutzung(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation einer Sondernutzung. """ + + __tablename__ = 'bp_komplexe_sondernutzung' + __avoidRelation__ = ['baugebiet'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_Sondernutzungen), nullable=False) + + nutzungText = Column(String) + aufschrift = Column(String) + + baugebiet_id = Column(UUID(as_uuid=True), ForeignKey('bp_baugebiet.id', ondelete='CASCADE')) + baugebiet = relationship('BP_BaugebietsTeilFlaeche', back_populates='rel_sondernutzung') + + @classmethod + def avoid_export(cls): + return ['baugebiet_id'] diff --git a/src/SAGisXPlanung/BPlan/BP_Bebauung/enums.py b/src/SAGisXPlanung/BPlan/BP_Bebauung/enums.py new file mode 100644 index 0000000..5bfe9d5 --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Bebauung/enums.py @@ -0,0 +1,65 @@ +from enum import Enum + +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin + + +class BP_Dachform(XPlanungEnumMixin, Enum): + """ Erlaubte Dachform innerhalb einer Bebauungsfläche. """ + + Flachdach = 1000 + Pultdach = 2100 + VersetztesPultdach = 2200 + GeneigtesDach = 3000 + Satteldach = 3100 + Walmdach = 3200 + KrueppelWalmdach = 3300 + Mansarddach = 3400 + Zeltdach = 3500 + Kegeldach = 3600 + Kuppeldach = 3700 + Sheddach = 3800 + Bogendach = 3900 + Turmdach = 4000 + Tonnendach = 4100 + Mischform = 5000 + Sonstiges = 9999 + + +class BP_Zulaessigkeit(XPlanungEnumMixin, Enum): + """ Für urbane Gebiete oder Teile solcher Gebiete kann festgesetzt werden, dass in Gebäuden im Erdgeschoss an der + Straßenseite eine Wohnnutzung nicht oder nur ausnahmsweise zulässig ist """ + + Zulaessig = 1000 + NichtZulaessig = 2000 + AusnahmsweiseZulaessig = 3000 + + +class BP_Bauweise(XPlanungEnumMixin, Enum): + """ Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB) """ + + KeineAngabe = None + OffeneBauweise = 1000 + GeschlosseneBauweise = 2000 + AbweichendeBauweise = 3000 + + +class BP_BebauungsArt(XPlanungEnumMixin, Enum): + """ Detaillierte Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB) """ + + Einzelhaeuser = 1000 + Doppelhaeuser = 2000 + Hausgruppen = 3000 + EinzelDoppelhaeuser = 4000 + EinzelhaeuserHausgruppen = 5000 + DoppelhaeuserHausgruppen = 6000 + Reihenhaeuser = 7000 + EinzelhaeuserDoppelhaeuserHausgruppen = 8000 + + +class BP_GrenzBebauung(XPlanungEnumMixin, Enum): + """ Festsetzung der Bebauung der vorderen Grundstücksgrenze (§9, Abs. 1, Nr. 2 BauGB) """ + + KeineAngabe = None + Verboten = 1000 + Erlaubt = 2000 + Erzwungen = 3000 diff --git a/src/SAGisXPlanung/BPlan/BP_Bebauung/feature_types.py b/src/SAGisXPlanung/BPlan/BP_Bebauung/feature_types.py new file mode 100644 index 0000000..48e2d9b --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Bebauung/feature_types.py @@ -0,0 +1,483 @@ +import logging +import os +import uuid +from typing import List + +from qgis.core import (QgsSymbol, QgsWkbTypes, QgsPointXY, QgsGeometry, QgsSingleSymbolRenderer, + QgsSimpleLineSymbolLayer, QgsLimitedRandomColorRamp, QgsRuleBasedRenderer, QgsSymbolLayerUtils, + QgsSimpleFillSymbolLayer) +from qgis.PyQt.QtGui import QColor, QIcon +from qgis.PyQt.QtCore import Qt, QSize, pyqtSlot +from qgis.utils import iface + +from geoalchemy2 import WKBElement +from sqlalchemy import Integer, Column, ForeignKey, Float, Enum, String, Boolean, event +from sqlalchemy.dialects.postgresql import ARRAY +from sqlalchemy.orm import relationship, load_only, joinedload, declared_attr + +from SAGisXPlanung import Session, XPlanVersion +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Objekt +from SAGisXPlanung.BPlan.BP_Bebauung.data_types import BP_Dachgestaltung +from SAGisXPlanung.BPlan.BP_Bebauung.enums import BP_Zulaessigkeit, BP_Bauweise, BP_BebauungsArt, BP_GrenzBebauung +from SAGisXPlanung.BuildingTemplateItem import BuildingTemplateData, BuildingTemplateCellDataType, BuildingTemplateItem +from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry +from SAGisXPlanung.XPlan.XP_Praesentationsobjekte.feature_types import XP_Nutzungsschablone +from SAGisXPlanung.XPlan.core import XPCol, XPRelationshipProperty +from SAGisXPlanung.XPlan.enums import (XP_AllgArtDerBaulNutzung, XP_BesondereArtDerBaulNutzung, XP_AbweichungBauNVOTypen, + XP_Sondernutzungen) +from SAGisXPlanung.XPlan.mixins import LineGeometry, PolygonGeometry, FlaechenschlussObjekt +from SAGisXPlanung.XPlan.types import Angle, Area, Length, Volume, Scale, ConformityException, GeometryType, XPEnum +from SAGisXPlanung.XPlanungItem import XPlanungItem + +logger = logging.getLogger(__name__) + + +class BP_BaugebietsTeilFlaeche(PolygonGeometry, FlaechenschlussObjekt, BP_Objekt): + """ Teil eines Baugebiets mit einheitlicher Art der baulichen Nutzung. """ + + def __init__(self): + super(BP_BaugebietsTeilFlaeche, self).__init__() + + self.id = uuid.uuid4() + template = XP_Nutzungsschablone() + template.dientZurDarstellungVon_id = self.id + self.wirdDargestelltDurch.append(template) + + self.xplan_item = XPlanungItem(xid=str(self.id), xtype=BP_BaugebietsTeilFlaeche) + + __tablename__ = 'bp_baugebiet' + __mapper_args__ = { + 'polymorphic_identity': 'bp_baugebiet', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + dachgestaltung = relationship("BP_Dachgestaltung", back_populates="baugebiet", cascade="all, delete", + passive_deletes=True) + + FR = Column(Angle) + # abweichungText [BP_TextAbschnitt] + MaxZahlWohnungen = Column(Integer) + MinGRWohneinheit = Column(Area) + Fmin = Column(Area) + Fmax = Column(Area) + Bmin = Column(Length) + Bmax = Column(Length) + Tmin = Column(Length) + Tmax = Column(Length) + GFZmin = Column(Float) + GFZmax = Column(Float) + GFZ = Column(Float) + GFZ_Ausn = Column(Float) + GFmin = Column(Area) + GFmax = Column(Area) + GF = Column(Area) + GF_Ausn = Column(Area) + BMZ = Column(Float) + BMZ_Ausn = Column(Float) + BM = Column(Volume) + BM_Ausn = Column(Volume) + GRZmin = Column(Float) + GRZmax = Column(Float) + GRZ = Column(Float) + GRZ_Ausn = Column(Float) + GRmin = Column(Area) + GRmax = Column(Area) + GR = Column(Area) + GR_Ausn = Column(Area) + Zmin = Column(Integer) + Zmax = Column(Integer) + Zzwingend = Column(Integer) + Z = Column(Integer) + Z_Ausn = Column(Integer) + Z_Staffel = Column(Integer) + Z_Dach = Column(Integer) + ZUmin = Column(Integer) + ZUmax = Column(Integer) + ZUzwingend = Column(Integer) + ZU = Column(Integer) + ZU_Ausn = Column(Integer) + wohnnutzungEGStrasse = Column(XPEnum(BP_Zulaessigkeit, include_default=True)) + ZWohn = Column(Integer) + GFAntWohnen = Column(Scale) + GFWohnen = Column(Area) + GFAntGewerbe = Column(Scale) + GFGewerbe = Column(Area) + VF = Column(Area) + allgArtDerBaulNutzung = Column(Enum(XP_AllgArtDerBaulNutzung)) + besondereArtDerBaulNutzung = Column(Enum(XP_BesondereArtDerBaulNutzung)) + + @declared_attr + def sondernutzung(cls): + return XPCol(ARRAY(Enum(XP_Sondernutzungen)), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_sondernutzung_attr) + + rel_sondernutzung = relationship("BP_KomplexeSondernutzung", back_populates="baugebiet", + cascade="all, delete", passive_deletes=True) + + nutzungText = XPCol(String, version=XPlanVersion.FIVE_THREE) + abweichungBauNVO = Column(Enum(XP_AbweichungBauNVOTypen)) + bauweise = Column(XPEnum(BP_Bauweise, include_default=True)) + vertikaleDifferenzierung = Column(Boolean) + bebauungsArt = Column(XPEnum(BP_BebauungsArt, include_default=True)) + bebauungVordereGrenze = Column(Enum(BP_GrenzBebauung)) + bebauungRueckwaertigeGrenze = Column(Enum(BP_GrenzBebauung)) + bebauungSeitlicheGrenze = Column(Enum(BP_GrenzBebauung)) + refGebaeudequerschnitt = relationship("XP_ExterneReferenz", back_populates="baugebiet", cascade="all, delete", + passive_deletes=True) + zugunstenVon = Column(String) + + @classmethod + def import_sondernutzung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'sondernutzung' + else: + return 'rel_sondernutzung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_sondernutzung', xplan_attribute='sondernutzung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def symbol(cls): + return QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + color_map = [ + ('Wohngebiet', '"allgArtDerBaulNutzung" LIKE \'WohnBauflaeche\'', QColor('#f4c3b4')), + ('Gemischtes Gebiet', '"allgArtDerBaulNutzung" LIKE \'GemischteBauflaeche\'', QColor('#ddc885')), + ('Gewerbliches Gebiet', '"allgArtDerBaulNutzung" LIKE \'GewerblicheBauflaeche\'', QColor('#aeb5ad')), + ('Sondergebiet', '"allgArtDerBaulNutzung" LIKE \'SonderBauflaeche\'', QColor('#fbad03')), + ('keine Nutzung', '"allgArtDerBaulNutzung" LIKE \'\'', QgsLimitedRandomColorRamp.randomColors(1)[0]) + ] + + renderer = QgsRuleBasedRenderer(cls.symbol()) + root_rule = renderer.rootRule() + + for label, expression, color_name in color_map: + rule = root_rule.children()[0].clone() + rule.setLabel(label) + rule.setFilterExpression(expression) + rule.symbol().setColor(color_name) + root_rule.appendChild(rule) + + root_rule.removeChildAt(0) + return renderer + + @classmethod + def previewIcon(cls): + return QIcon(os.path.abspath(os.path.join(os.path.dirname(__file__), + '../../symbole/BP_Bebauung/BP_BaugebietsTeilFlaeche.svg'))) + + @classmethod + def attributes(cls): + return ['allgArtDerBaulNutzung'] + + def toCanvas(self, layer_group, plan_xid=None): + self.xplan_item.plan_xid = str(plan_xid) + super(BP_BaugebietsTeilFlaeche, self).toCanvas(layer_group, plan_xid) + + def asFeature(self, fields=None): + feat = super(BP_BaugebietsTeilFlaeche, self).asFeature(fields) + + # return early when `XP_Nutzungsschablone` should not be shown + template = self.template() + if template.hidden: + return feat + + if not template.position: + geom = feat.geometry().centroid() + point = geom.asPoint() + template.position = WKBElement(geom.asWkb(), srid=self.position.srid) + else: + point = template.geometry().asPoint() + + cells = template.data_attributes + rows = template.zeilenAnz + scale = template.skalierung + angle = template.drehwinkel + table = BuildingTemplateItem(iface.mapCanvas(), point, rows, self.usageTemplateData(cells), + parent=self.xplan_item, scale=scale, angle=angle) + # connect to signal on event filter, because QGraphicsItems can't emit signals + table.event_filter.positionUpdated.connect(lambda p: self.onTemplatePositionUpdated(p)) + MapLayerRegistry().addCanvasItem(table, str(self.id)) + + return feat + + def onTemplatePositionUpdated(self, pos: QgsPointXY): + with Session.begin() as session: + _self = session.query(self.xplan_item.xtype).options( + load_only('position'), + joinedload('wirdDargestelltDurch') + ).get(self.xplan_item.xid) + geom = QgsGeometry.fromPointXY(pos) + _self.template().position = WKBElement(geom.asWkb(), srid=_self.position.srid) + + def template(self): + return next((x for x in self.wirdDargestelltDurch if isinstance(x, XP_Nutzungsschablone)), None) + + def usageTemplateData(self, cells=None): + + def f(cell_type, item, additional_data=None): + return BuildingTemplateData.fromAttribute(item, cell_type, additional_data) + + cell_mapping = { + BuildingTemplateCellDataType.ArtDerBaulNutzung: (self.allgArtDerBaulNutzung, self.besondereArtDerBaulNutzung), + BuildingTemplateCellDataType.ZahlVollgeschosse: (self.Z,), + BuildingTemplateCellDataType.GRZ: (self.GRZ,), + BuildingTemplateCellDataType.GFZ: (self.GFZ,), + BuildingTemplateCellDataType.BebauungsArt: (self.bebauungsArt,), + BuildingTemplateCellDataType.Bauweise: (self.bauweise,), + BuildingTemplateCellDataType.Dachneigung: + ('',) if not self.dachgestaltung else (self.dachgestaltung[0].DN, + (self.dachgestaltung[0].DNmin, self.dachgestaltung[0].DNmax)), + BuildingTemplateCellDataType.Dachform: + ('',) if not self.dachgestaltung else (self.dachgestaltung[0].dachform, ) + } + + if cells is None: + cells = BuildingTemplateCellDataType.as_default() + + return [f(cell_type, *cell_mapping[cell_type]) for cell_type in cells] + + def layer_fields(self): + return { + 'allgArtDerBaulNutzung': self.allgArtDerBaulNutzung.name if self.allgArtDerBaulNutzung else '' + } + + def validate(self): + if (self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Kleinsiedlungsgebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.ReinesWohngebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.AllgWohngebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.BesonderesWohngebiet.name) and ( + self.allgArtDerBaulNutzung != XP_AllgArtDerBaulNutzung.WohnBauflaeche.name): + raise ConformityException(f'Wenn besondereArtDerBaulNutzung den Wert ' + f'{self.besondereArtDerBaulNutzung} hat, ' + f'muss allgArtDerBaulNutzung den Wert ' + f'{XP_AllgArtDerBaulNutzung(1000)} haben', + '4.5.1.2', self.__class__.__name__) + + if (self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Dorfgebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Mischgebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.UrbanesGebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Kerngebiet.name) and ( + self.allgArtDerBaulNutzung != XP_AllgArtDerBaulNutzung.GemischteBauflaeche.name): + raise ConformityException(f'Wenn besondereArtDerBaulNutzung den Wert ' + f'{self.besondereArtDerBaulNutzung} hat, ' + f'muss allgArtDerBaulNutzung den Wert ' + f'{XP_AllgArtDerBaulNutzung(2000)} haben', + '4.5.1.2', self.__class__.__name__) + + if (self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Gewerbegebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Industriegebiet.name) and ( + self.allgArtDerBaulNutzung != XP_AllgArtDerBaulNutzung.GewerblicheBauflaeche.name): + raise ConformityException(f'Wenn besondereArtDerBaulNutzung den Wert ' + f'{self.besondereArtDerBaulNutzung} hat, ' + f'muss allgArtDerBaulNutzung den Wert ' + f'{XP_AllgArtDerBaulNutzung(3000)} haben', + '4.5.1.2', self.__class__.__name__) + + if (self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Sondergebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.SondergebietErholung.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.SondergebietSonst.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Wochenendhausgebiet.name) and ( + self.allgArtDerBaulNutzung != XP_AllgArtDerBaulNutzung.SonderBauflaeche.name): + raise ConformityException(f'Wenn besondereArtDerBaulNutzung den Wert ' + f'{self.besondereArtDerBaulNutzung} hat, ' + f'muss allgArtDerBaulNutzung den Wert ' + f'{XP_AllgArtDerBaulNutzung(4000)} haben', + '4.5.1.2', self.__class__.__name__) + + erholung_sondernutzungen = [XP_Sondernutzungen.Wochendhausgebiet.name, XP_Sondernutzungen.Ferienhausgebiet.name, + XP_Sondernutzungen.Campingplatzgebiet.name, XP_Sondernutzungen.Kurgebiet.name, + XP_Sondernutzungen.SonstSondergebietErholung.name] + if (self.sondernutzung and not set(self.sondernutzung).isdisjoint(set(erholung_sondernutzungen))) and \ + self.besondereArtDerBaulNutzung != XP_BesondereArtDerBaulNutzung.SondergebietErholung.name: + raise ConformityException(f'Wenn sonderNutzung den Wert {self.sondernutzung} hat, ' + f'muss besondereArtDerBaulNutzung den Wert ' + f'{XP_BesondereArtDerBaulNutzung(2000)} haben', + '4.5.1.2', self.__class__.__name__) + if (self.sondernutzung and set(self.sondernutzung).isdisjoint(set(erholung_sondernutzungen))) and \ + self.besondereArtDerBaulNutzung != XP_BesondereArtDerBaulNutzung.SondergebietSonst.name: + raise ConformityException(f'Wenn sonderNutzung den Wert {self.sondernutzung} hat, ' + f'muss besondereArtDerBaulNutzung den Wert ' + f'{XP_BesondereArtDerBaulNutzung(2100)} haben', + '4.5.1.2', self.__class__.__name__) + + +@event.listens_for(BP_BaugebietsTeilFlaeche, 'load') +def receive_load(target, context): + target.xplan_item = XPlanungItem(xid=str(target.id), xtype=BP_BaugebietsTeilFlaeche) + + +class BP_BauGrenze(LineGeometry, BP_Objekt): + """ Festsetzung einer Baugrenze (§9 Abs. 1 Nr. 2 BauGB, §22 und 23 BauNVO). """ + + __tablename__ = 'bp_baugrenze' + __mapper_args__ = { + 'polymorphic_identity': 'bp_baugrenze', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + bautiefe = Column(Length) + geschossMin = Column(Integer) + geschossMax = Column(Integer) + + @classmethod + def symbol(cls): + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.LineGeometry) + symbol.deleteSymbolLayer(0) + + colored_strip = QgsSimpleLineSymbolLayer.create({}) + colored_strip.setColor(QColor('#1e8ebe')) + colored_strip.setWidth(0.8) + colored_strip.setOffset(0.3) + + border = QgsSimpleLineSymbolLayer.create({}) + border.setColor(QColor(0, 0, 0)) + border.setWidth(0.3) + border.setPenStyle(Qt.DashDotLine) + + symbol.appendSymbolLayer(colored_strip) + symbol.appendSymbolLayer(border) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) + + +class BP_BauLinie(LineGeometry, BP_Objekt): + """ Festsetzung einer Baulinie (§9 Abs. 1 Nr. 2 BauGB, §22 und 23 BauNVO). """ + + __tablename__ = 'bp_baulinie' + __mapper_args__ = { + 'polymorphic_identity': 'bp_baulinie', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + bautiefe = Column(Length) + geschossMin = Column(Integer) + geschossMax = Column(Integer) + + @classmethod + def symbol(cls): + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.LineGeometry) + symbol.deleteSymbolLayer(0) + + colored_strip = QgsSimpleLineSymbolLayer.create({}) + colored_strip.setColor(QColor('#e95c4a')) + colored_strip.setWidth(0.8) + colored_strip.setOffset(0.3) + + border = QgsSimpleLineSymbolLayer.create({}) + border.setColor(QColor(0, 0, 0)) + border.setWidth(0.3) + border.setPenStyle(Qt.DashDotDotLine) + + symbol.appendSymbolLayer(colored_strip) + symbol.appendSymbolLayer(border) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) + + +class BP_BesondererNutzungszweckFlaeche(PolygonGeometry, BP_Objekt): + """ Festsetzung einer Fläche mit besonderem Nutzungszweck, der durch besondere städtebauliche Gründe erfordert + wird (§9 Abs. 1 Nr. 9 BauGB.). """ + + __tablename__ = 'bp_besondere_nutzung' + __mapper_args__ = { + 'polymorphic_identity': 'bp_besondere_nutzung', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + dachgestaltung = relationship("BP_Dachgestaltung", back_populates="besondere_nutzung", cascade="all, delete", + passive_deletes=True) + + FR = Column(Angle) + MaxZahlWohnungen = Column(Integer) + MinGRWohneinheit = Column(Area) + Fmin = Column(Area) + Fmax = Column(Area) + Bmin = Column(Length) + Bmax = Column(Length) + Tmin = Column(Length) + Tmax = Column(Length) + GFZmin = Column(Float) + GFZmax = Column(Float) + GFZ = Column(Float) + GFZ_Ausn = Column(Float) + GFmin = Column(Area) + GFmax = Column(Area) + GF = Column(Area) + GF_Ausn = Column(Area) + BMZ = Column(Float) + BMZ_Ausn = Column(Float) + BM = Column(Volume) + BM_Ausn = Column(Volume) + GRZmin = Column(Float) + GRZmax = Column(Float) + GRZ = Column(Float) + GRZ_Ausn = Column(Float) + GRmin = Column(Area) + GRmax = Column(Area) + GR = Column(Area) + GR_Ausn = Column(Area) + Zmin = Column(Integer) + Zmax = Column(Integer) + Zzwingend = Column(Integer) + Z = Column(Integer) + Z_Ausn = Column(Integer) + Z_Staffel = Column(Integer) + Z_Dach = Column(Integer) + ZUmin = Column(Integer) + ZUmax = Column(Integer) + ZUzwingend = Column(Integer) + ZU = Column(Integer) + ZU_Ausn = Column(Integer) + zweckbestimmung = Column(String) + bauweise = Column(XPEnum(BP_Bauweise, include_default=True)) + bebauungsArt = Column(XPEnum(BP_BebauungsArt, include_default=True)) + + @classmethod + def symbol(cls): + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.3) + + fill = QgsSimpleFillSymbolLayer.create({}) + fill.setFillColor(QColor('white')) + + symbol.appendSymbolLayer(line) + symbol.appendSymbolLayer(fill) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) + diff --git a/src/SAGisXPlanung/BPlan/BP_Gemeinbedarf_Spiel_und_Sportanlagen/__init__.py b/src/SAGisXPlanung/BPlan/BP_Gemeinbedarf_Spiel_und_Sportanlagen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/BPlan/BP_Gemeinbedarf_Spiel_und_Sportanlagen/data_types.py b/src/SAGisXPlanung/BPlan/BP_Gemeinbedarf_Spiel_und_Sportanlagen/data_types.py new file mode 100644 index 0000000..d5d3a49 --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Gemeinbedarf_Spiel_und_Sportanlagen/data_types.py @@ -0,0 +1,51 @@ +from uuid import uuid4 + +from sqlalchemy import Column, Enum, String, ForeignKey +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from SAGisXPlanung import Base +from SAGisXPlanung.XPlan.enums import XP_ZweckbestimmungSpielSportanlage, XP_ZweckbestimmungGemeinbedarf +from SAGisXPlanung.XPlan.mixins import RelationshipMixin, ElementOrderMixin + + +class BP_KomplexeZweckbestSpielSportanlage(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung einer Spiel-/ Sportanlage. """ + + __tablename__ = 'bp_zweckbestimmung_sport' + __avoidRelation__ = ['spiel_sportanlage'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_ZweckbestimmungSpielSportanlage), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + spiel_sportanlage_id = Column(UUID(as_uuid=True), ForeignKey('bp_spiel_sportanlage.id', ondelete='CASCADE')) + spiel_sportanlage = relationship('BP_SpielSportanlagenFlaeche', back_populates='rel_zweckbestimmung') + + @classmethod + def avoid_export(cls): + return ['spiel_sportanlage_id'] + + +class BP_KomplexeZweckbestGemeinbedarf(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung einer Gemeinbedarfsfläche """ + + __tablename__ = 'bp_zweckbestimmung_gemeinbedarf' + __avoidRelation__ = ['gemeinbedarf'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_ZweckbestimmungGemeinbedarf), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + gemeinbedarf_id = Column(UUID(as_uuid=True), ForeignKey('bp_gemeinbedarf.id', ondelete='CASCADE')) + gemeinbedarf = relationship('BP_GemeinbedarfsFlaeche', back_populates='rel_zweckbestimmung') + + @classmethod + def avoid_export(cls): + return ['gemeinbedarf_id'] diff --git a/src/SAGisXPlanung/BPlan/BP_Gemeinbedarf_Spiel_und_Sportanlagen/feature_types.py b/src/SAGisXPlanung/BPlan/BP_Gemeinbedarf_Spiel_und_Sportanlagen/feature_types.py new file mode 100644 index 0000000..31399bb --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Gemeinbedarf_Spiel_und_Sportanlagen/feature_types.py @@ -0,0 +1,274 @@ +from typing import List + +from qgis.core import (QgsSimpleFillSymbolLayer, QgsSymbol, QgsWkbTypes, QgsSingleSymbolRenderer, QgsSymbolLayerUtils, + QgsSimpleLineSymbolLayer, QgsMarkerLineSymbolLayer, QgsCentroidFillSymbolLayer, + QgsSvgMarkerSymbolLayer, QgsMarkerSymbol, QgsUnitTypes, QgsMapUnitScale, QgsRuleBasedRenderer) +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import QSize + +from sqlalchemy import Column, ForeignKey, String, Integer, Float, Enum +from sqlalchemy.orm import relationship, declared_attr + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Objekt +from SAGisXPlanung.BPlan.BP_Bebauung.enums import BP_BebauungsArt, BP_Bauweise +from SAGisXPlanung.RuleBasedSymbolRenderer import RuleBasedSymbolRenderer +from SAGisXPlanung.XPlan.core import XPCol, XPRelationshipProperty +from SAGisXPlanung.XPlan.enums import XP_ZweckbestimmungSpielSportanlage, XP_ZweckbestimmungGemeinbedarf, \ + XP_Traegerschaft +from SAGisXPlanung.XPlan.mixins import PolygonGeometry, FlaechenschlussObjekt +from SAGisXPlanung.XPlan.types import Area, Volume, Length, Angle, GeometryType, XPEnum + + +class BP_GemeinbedarfsFlaeche(PolygonGeometry, FlaechenschlussObjekt, BP_Objekt): + """ Einrichtungen und Anlagen zur Versorgung mit Gütern und Dienstleistungen des öffentlichen und privaten + Bereichs, hier Flächen für den Gemeindebedarf (§9, Abs. 1, Nr.5 und Abs. 6 BauGB).""" + + __tablename__ = 'bp_gemeinbedarf' + __mapper_args__ = { + 'polymorphic_identity': 'bp_gemeinbedarf', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + dachgestaltung = relationship("BP_Dachgestaltung", back_populates="gemeinbedarf", cascade="all, delete", + passive_deletes=True) + + FR = Column(Angle) + MaxZahlWohnungen = Column(Integer) + MinGRWohneinheit = Column(Area) + Fmin = Column(Area) + Fmax = Column(Area) + Bmin = Column(Length) + Bmax = Column(Length) + Tmin = Column(Length) + Tmax = Column(Length) + GFZmin = Column(Float) + GFZmax = Column(Float) + GFZ = Column(Float) + GFZ_Ausn = Column(Float) + GFmin = Column(Area) + GFmax = Column(Area) + GF = Column(Area) + GF_Ausn = Column(Area) + BMZ = Column(Float) + BMZ_Ausn = Column(Float) + BM = Column(Volume) + BM_Ausn = Column(Volume) + GRZmin = Column(Float) + GRZmax = Column(Float) + GRZ = Column(Float) + GRZ_Ausn = Column(Float) + GRmin = Column(Area) + GRmax = Column(Area) + GR = Column(Area) + GR_Ausn = Column(Area) + Zmin = Column(Integer) + Zmax = Column(Integer) + Zzwingend = Column(Integer) + Z = Column(Integer) + Z_Ausn = Column(Integer) + Z_Staffel = Column(Integer) + Z_Dach = Column(Integer) + ZUmin = Column(Integer) + ZUmax = Column(Integer) + ZUzwingend = Column(Integer) + ZU = Column(Integer) + ZU_Ausn = Column(Integer) + + @declared_attr + def zweckbestimmung(cls): + return XPCol(Enum(XP_ZweckbestimmungGemeinbedarf), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_zweckbestimmung_attr) + + rel_zweckbestimmung = relationship("BP_KomplexeZweckbestGemeinbedarf", back_populates="gemeinbedarf", + cascade="all, delete", passive_deletes=True) + + bauweise = Column(XPEnum(BP_Bauweise, include_default=True)) + bebauungsArt = Column(XPEnum(BP_BebauungsArt, include_default=True)) + traeger = XPCol(XPEnum(XP_Traegerschaft, include_default=True), version=XPlanVersion.SIX) + zugunstenVon = Column(String) + + __icon_map__ = [ + ('Öffentliche Verwaltung', '"zweckbestimmung" LIKE \'10%\'', 'Oeffentliche_Verwaltung.svg'), + ('Bildung und Forschung', '"zweckbestimmung" LIKE \'12%\'', 'Bildung_Forschung.svg'), + ('Kirchliche Einrichtung', '"zweckbestimmung" LIKE \'14%\'', 'Kirchliche_Einrichtung.svg'), + ('Soziale Einrichtung', '"zweckbestimmung" LIKE \'16%\'', 'Einrichtung_Soziales.svg'), + ('Gesundheit', '"zweckbestimmung" LIKE \'18%\'', 'Krankenhaus.svg'), + ('Kulturelle Einrichtung', '"zweckbestimmung" LIKE \'20%\'', 'Einrichtung_Kultur.svg'), + ('Sicherheit/Ordnung', '"zweckbestimmung" LIKE \'2400\'', 'Polizei.svg'), + ('Feuerwehr', '"zweckbestimmung" LIKE \'24000\'', 'Feuerwehr.svg'), + ('Schutzbauwerk', '"zweckbestimmung" LIKE \'24001\'', 'Schutzbauwerk.svg'), + ('Justiz', '"zweckbestimmung" LIKE \'24002\'', 'Justizvollzug.svg'), + ('Post', '"zweckbestimmung" LIKE \'26000\'', 'Post.svg'), + ('Sonstiges', '"zweckbestimmung" LIKE \'\'', ''), + ] + + def layer_fields(self): + return { + 'zweckbestimmung': self.zweckbestimmung.value if self.zweckbestimmung else '', + 'skalierung': self.skalierung if self.skalierung else '', + 'drehwinkel': self.drehwinkel if self.drehwinkel else '' + } + + @classmethod + def import_zweckbestimmung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'zweckbestimmung' + else: + return 'rel_zweckbestimmung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_zweckbestimmung', xplan_attribute='zweckbestimmung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def attributes(cls): + return ['zweckbestimmung', 'skalierung', 'drehwinkel'] + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#febae1')) + symbol.appendSymbolLayer(fill) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + renderer = RuleBasedSymbolRenderer(cls.__icon_map__, cls.symbol(), 'BP_Gemeinbedarf_Spiel_und_Sportanlagen') + return renderer + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) + + +class BP_SpielSportanlagenFlaeche(PolygonGeometry, FlaechenschlussObjekt, BP_Objekt): + """ Einrichtungen und Anlagen zur Versorgung mit Gütern und Dienstleistungen des öffentlichen und privaten + Bereichs, hier Flächen für Sport- und Spielanlagen (§9, Abs. 1, Nr. 5 und Abs. 6 BauGB). """ + + __tablename__ = 'bp_spiel_sportanlage' + __mapper_args__ = { + 'polymorphic_identity': 'bp_spiel_sportanlage', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + MaxZahlWohnungen = Column(Integer) + MinGRWohneinheit = Column(Area) + Fmin = Column(Area) + Fmax = Column(Area) + Bmin = Column(Length) + Bmax = Column(Length) + Tmin = Column(Length) + Tmax = Column(Length) + GFZmin = Column(Float) + GFZmax = Column(Float) + GFZ = Column(Float) + GFZ_Ausn = Column(Float) + GFmin = Column(Area) + GFmax = Column(Area) + GF = Column(Area) + GF_Ausn = Column(Area) + BMZ = Column(Float) + BMZ_Ausn = Column(Float) + BM = Column(Volume) + BM_Ausn = Column(Volume) + GRZmin = Column(Float) + GRZmax = Column(Float) + GRZ = Column(Float) + GRZ_Ausn = Column(Float) + GRmin = Column(Area) + GRmax = Column(Area) + GR = Column(Area) + GR_Ausn = Column(Area) + Zmin = Column(Integer) + Zmax = Column(Integer) + Zzwingend = Column(Integer) + Z = Column(Integer) + Z_Ausn = Column(Integer) + Z_Staffel = Column(Integer) + Z_Dach = Column(Integer) + ZUmin = Column(Integer) + ZUmax = Column(Integer) + ZUzwingend = Column(Integer) + ZU = Column(Integer) + ZU_Ausn = Column(Integer) + + @declared_attr + def zweckbestimmung(cls): + return XPCol(Enum(XP_ZweckbestimmungSpielSportanlage), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_zweckbestimmung_attr) + + rel_zweckbestimmung = relationship("BP_KomplexeZweckbestSpielSportanlage", back_populates="spiel_sportanlage", + cascade="all, delete", passive_deletes=True) + zugunstenVon = Column(String) + + __icon_map__ = [ + ('Sportanlage', '"zweckbestimmung" LIKE \'Sportanlage\'', 'Anlage_Sportanlage.svg'), + ('Spielanlage', '"zweckbestimmung" LIKE \'Spielanlage\'', 'Anlage_Spielanlage.svg'), + ('Gemischt/Sonstiges', '"zweckbestimmung" LIKE \'\'', ''), + ] + + def layer_fields(self): + return { + 'zweckbestimmung': self.zweckbestimmung.name if self.zweckbestimmung else '', + 'skalierung': self.skalierung if self.skalierung else '', + 'drehwinkel': self.drehwinkel if self.drehwinkel else '' + } + + @classmethod + def import_zweckbestimmung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'zweckbestimmung' + else: + return 'rel_zweckbestimmung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_zweckbestimmung', xplan_attribute='zweckbestimmung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def attributes(cls): + return ['zweckbestimmung', 'skalierung', 'drehwinkel'] + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + border = QgsSimpleLineSymbolLayer() + border.setColor(QColor(0, 0, 0)) + border.setWidth(0.3) + + dotted_line_inner = QgsMarkerLineSymbolLayer() + dotted_line_inner.setColor(QColor(0, 0, 0)) + dotted_line_inner.setWidth(0.5) + dotted_line_inner.setOffset(1.5) + + dotted_line_outer = dotted_line_inner.clone() + dotted_line_outer.setOffset(0.8) + + symbol.appendSymbolLayer(border) + symbol.appendSymbolLayer(dotted_line_inner) + symbol.appendSymbolLayer(dotted_line_outer) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + renderer = RuleBasedSymbolRenderer(cls.__icon_map__, cls.symbol(), 'BP_Gemeinbedarf_Spiel_und_Sportanlagen') + return renderer + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) diff --git a/src/SAGisXPlanung/BPlan/BP_Landwirtschaft_Wald_und_Gruenflaechen/__init__.py b/src/SAGisXPlanung/BPlan/BP_Landwirtschaft_Wald_und_Gruenflaechen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/BPlan/BP_Landwirtschaft_Wald_und_Gruenflaechen/data_types.py b/src/SAGisXPlanung/BPlan/BP_Landwirtschaft_Wald_und_Gruenflaechen/data_types.py new file mode 100644 index 0000000..e887641 --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Landwirtschaft_Wald_und_Gruenflaechen/data_types.py @@ -0,0 +1,72 @@ +from uuid import uuid4 + +from sqlalchemy import Column, Enum, String, ForeignKey +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from SAGisXPlanung import Base +from SAGisXPlanung.XPlan.enums import XP_ZweckbestimmungGruen, XP_ZweckbestimmungLandwirtschaft, XP_ZweckbestimmungWald +from SAGisXPlanung.XPlan.mixins import RelationshipMixin, ElementOrderMixin + + +class BP_KomplexeZweckbestGruen(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung einer Grünfläche. """ + + __tablename__ = 'bp_zweckbestimmung_gruen' + __avoidRelation__ = ['gruenflaeche'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_ZweckbestimmungGruen), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + gruenflaeche_id = Column(UUID(as_uuid=True), ForeignKey('bp_gruenflaeche.id', ondelete='CASCADE')) + gruenflaeche = relationship('BP_GruenFlaeche', back_populates='rel_zweckbestimmung') + + @classmethod + def avoid_export(cls): + return ['gruenflaeche_id'] + + +class BP_KomplexeZweckbestLandwirtschaft(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung für Landwirtschaft. """ + + __tablename__ = 'bp_zweckbestimmung_landwirtschaft' + __avoidRelation__ = ['landwirtschaft'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_ZweckbestimmungLandwirtschaft), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + landwirtschaft_id = Column(UUID(as_uuid=True), ForeignKey('bp_landwirtschaft.id', ondelete='CASCADE')) + landwirtschaft = relationship('BP_LandwirtschaftsFlaeche', back_populates='rel_zweckbestimmung') + + @classmethod + def avoid_export(cls): + return ['landwirtschaft_id'] + + +class BP_KomplexeZweckbestWald(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung für Waldflächen. """ + + __tablename__ = 'bp_zweckbestimmung_wald' + __avoidRelation__ = ['waldflaeche'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_ZweckbestimmungWald), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + waldflaeche_id = Column(UUID(as_uuid=True), ForeignKey('bp_wald.id', ondelete='CASCADE')) + waldflaeche = relationship('BP_WaldFlaeche', back_populates='rel_zweckbestimmung') + + @classmethod + def avoid_export(cls): + return ['waldflaeche'] diff --git a/src/SAGisXPlanung/BPlan/BP_Landwirtschaft_Wald_und_Gruenflaechen/feature_types.py b/src/SAGisXPlanung/BPlan/BP_Landwirtschaft_Wald_und_Gruenflaechen/feature_types.py new file mode 100644 index 0000000..e1e2fe1 --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Landwirtschaft_Wald_und_Gruenflaechen/feature_types.py @@ -0,0 +1,245 @@ +import logging +from typing import List + +from qgis.core import (QgsSymbol, QgsWkbTypes, QgsSingleSymbolRenderer, QgsSymbolLayerUtils, QgsSimpleFillSymbolLayer) +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import QSize + +from lxml import etree +from sqlalchemy import Integer, Column, ForeignKey, Float, Enum, String +from sqlalchemy.orm import relationship, declared_attr + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Objekt +from SAGisXPlanung.RuleBasedSymbolRenderer import RuleBasedSymbolRenderer +from SAGisXPlanung.XPlan.core import XPCol, XPRelationshipProperty +from SAGisXPlanung.XPlan.enums import XP_Nutzungsform, XP_ZweckbestimmungLandwirtschaft, XP_ZweckbestimmungWald, \ + XP_EigentumsartWald, XP_WaldbetretungTyp, XP_ZweckbestimmungGruen +from SAGisXPlanung.XPlan.mixins import PolygonGeometry, FlaechenschlussObjekt +from SAGisXPlanung.XPlan.types import Area, Length, Volume, GeometryType, XPEnum + + +logger = logging.getLogger(__name__) + + +class BP_GruenFlaeche(PolygonGeometry, FlaechenschlussObjekt, BP_Objekt): + """ Festsetzungen von öffentlichen und privaten Grünflächen (§ 9, Abs. 1, Nr. 15 BauGB).""" + + __tablename__ = 'bp_gruenflaeche' + __mapper_args__ = { + 'polymorphic_identity': 'bp_gruenflaeche', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + MaxZahlWohnungen = Column(Integer) + MinGRWohneinheit = Column(Area) + Fmin = Column(Area) + Fmax = Column(Area) + Bmin = Column(Length) + Bmax = Column(Length) + Tmin = Column(Length) + Tmax = Column(Length) + GFZmin = Column(Float) + GFZmax = Column(Float) + GFZ = Column(Float) + GFZ_Ausn = Column(Float) + GFmin = Column(Area) + GFmax = Column(Area) + GF = Column(Area) + GF_Ausn = Column(Area) + BMZ = Column(Float) + BMZ_Ausn = Column(Float) + BM = Column(Volume) + BM_Ausn = Column(Volume) + GRZmin = Column(Float) + GRZmax = Column(Float) + GRZ = Column(Float) + GRZ_Ausn = Column(Float) + GRmin = Column(Area) + GRmax = Column(Area) + GR = Column(Area) + GR_Ausn = Column(Area) + Zmin = Column(Integer) + Zmax = Column(Integer) + Zzwingend = Column(Integer) + Z = Column(Integer) + Z_Ausn = Column(Integer) + Z_Staffel = Column(Integer) + Z_Dach = Column(Integer) + ZUmin = Column(Integer) + ZUmax = Column(Integer) + ZUzwingend = Column(Integer) + ZU = Column(Integer) + ZU_Ausn = Column(Integer) + + @declared_attr + def zweckbestimmung(cls): + return XPCol(Enum(XP_ZweckbestimmungGruen), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_zweckbestimmung_attr) + + rel_zweckbestimmung = relationship("BP_KomplexeZweckbestGruen", back_populates="gruenflaeche", + cascade="all, delete", passive_deletes=True) + + nutzungsform = Column(XPEnum(XP_Nutzungsform, include_default=True)) + zugunstenVon = Column(String) + + __icon_map__ = [ + ('Parkanlage', '"zweckbestimmung" LIKE \'10%\'', 'Parkanlage.svg'), + ('Dauerkleingärten', '"zweckbestimmung" LIKE \'12%\'', 'Dauerkleingärten.svg'), + ('Sportanlage', '"zweckbestimmung" LIKE \'14%\'', 'Sportplatz.svg'), + ('Spielplatz', '"zweckbestimmung" LIKE \'16%\'', 'Spielplatz.svg'), + ('Zeltplatz', '"zweckbestimmung" LIKE \'18%\'', 'Zeltplatz.svg'), + ('Badeplatz', '"zweckbestimmung" LIKE \'20%\'', 'Badeplatz.svg'), + ('Friedhof', '"zweckbestimmung" LIKE \'26%\'', 'Friedhof.svg'), + ('Sonstiges', '"zweckbestimmung" LIKE \'\'', ''), + ] + + def layer_fields(self): + return { + 'zweckbestimmung': self.zweckbestimmung.value if self.zweckbestimmung else '', + 'skalierung': self.skalierung if self.skalierung else '', + 'drehwinkel': self.drehwinkel if self.drehwinkel else '' + } + + @classmethod + def import_zweckbestimmung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'zweckbestimmung' + else: + return 'rel_zweckbestimmung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_zweckbestimmung', xplan_attribute='zweckbestimmung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def attributes(cls): + return ['zweckbestimmung', 'skalierung', 'drehwinkel'] + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#8fe898')) + symbol.appendSymbolLayer(fill) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + renderer = RuleBasedSymbolRenderer(cls.__icon_map__, cls.symbol(), 'BP_Landwirtschaft_Wald_und_Gruenflaechen') + return renderer + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) + + +class BP_LandwirtschaftsFlaeche(PolygonGeometry, FlaechenschlussObjekt, BP_Objekt): + """ Festsetzungen für die Landwirtschaft (§ 9, Abs. 1, Nr. 18a BauGB) """ + + __tablename__ = 'bp_landwirtschaft' + __mapper_args__ = { + 'polymorphic_identity': 'bp_landwirtschaft', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + @declared_attr + def zweckbestimmung(cls): + return XPCol(Enum(XP_ZweckbestimmungLandwirtschaft), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_zweckbestimmung_attr) + + rel_zweckbestimmung = relationship("BP_KomplexeZweckbestLandwirtschaft", back_populates="landwirtschaft", + cascade="all, delete", passive_deletes=True) + + @classmethod + def import_zweckbestimmung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'zweckbestimmung' + else: + return 'rel_zweckbestimmung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_zweckbestimmung', xplan_attribute='zweckbestimmung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#befe9d')) + + symbol.appendSymbolLayer(fill) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) + + +class BP_WaldFlaeche(PolygonGeometry, FlaechenschlussObjekt, BP_Objekt): + """ Festsetzung von Waldflächen (§ 9, Abs. 1, Nr. 18b BauGB). """ + + __tablename__ = 'bp_wald' + __mapper_args__ = { + 'polymorphic_identity': 'bp_wald', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + @declared_attr + def zweckbestimmung(cls): + return XPCol(Enum(XP_ZweckbestimmungWald), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_zweckbestimmung_attr) + + rel_zweckbestimmung = relationship("BP_KomplexeZweckbestWald", back_populates="waldflaeche", + cascade="all, delete", passive_deletes=True) + + eigentumsart = Column(Enum(XP_EigentumsartWald)) + betreten = Column(Enum(XP_WaldbetretungTyp)) + + @classmethod + def import_zweckbestimmung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'zweckbestimmung' + else: + return 'rel_zweckbestimmung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_zweckbestimmung', xplan_attribute='zweckbestimmung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#17a8a5')) + + symbol.appendSymbolLayer(fill) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) diff --git a/src/SAGisXPlanung/BPlan/BP_Naturschutz_Landschaftsbild_Naturhaushalt/__init__.py b/src/SAGisXPlanung/BPlan/BP_Naturschutz_Landschaftsbild_Naturhaushalt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/BPlan/BP_Naturschutz_Landschaftsbild_Naturhaushalt/feature_types.py b/src/SAGisXPlanung/BPlan/BP_Naturschutz_Landschaftsbild_Naturhaushalt/feature_types.py new file mode 100644 index 0000000..27bb7c1 --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Naturschutz_Landschaftsbild_Naturhaushalt/feature_types.py @@ -0,0 +1,170 @@ +import os + +from qgis.core import (QgsSymbol, QgsWkbTypes, QgsSingleSymbolRenderer, QgsSymbolLayerUtils, QgsSimpleLineSymbolLayer, + QgsUnitTypes, QgsSimpleFillSymbolLayer, QgsSimpleMarkerSymbolLayerBase, + QgsSimpleMarkerSymbolLayer, QgsMarkerLineSymbolLayer, QgsMarkerSymbol) +from qgis.PyQt.QtGui import QIcon, QColor +from qgis.PyQt.QtCore import QSize, Qt + +from sqlalchemy import Integer, Column, ForeignKey, Enum, String, Boolean +from sqlalchemy.orm import relationship + +from SAGisXPlanung import BASE_DIR, XPlanVersion +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Objekt +from SAGisXPlanung.RuleBasedSymbolRenderer import RuleBasedSymbolRenderer +from SAGisXPlanung.XPlan.core import XPCol +from SAGisXPlanung.XPlan.enums import XP_ABEMassnahmenTypen, XP_AnpflanzungBindungErhaltungsGegenstand, XP_SPEZiele +from SAGisXPlanung.XPlan.mixins import PointGeometry, PolygonGeometry, MixedGeometry +from SAGisXPlanung.XPlan.types import Length, GeometryType + + +class BP_AnpflanzungBindungErhaltung(MixedGeometry, BP_Objekt): + """ Festsetzung des Anpflanzens von Bäumen, Sträuchern und sonstigen Bepflanzungen; + Festsetzung von Bindungen für Bepflanzungen und für die Erhaltung von Bäumen, Sträuchern und sonstigen + Bepflanzungen sowie von Gewässern; (§9 Abs. 1 Nr. 25 und Abs. 4 BauGB) """ + + __tablename__ = 'bp_pflanzung' + __mapper_args__ = { + 'polymorphic_identity': 'bp_pflanzung', + } + __rule_based_renderers__ = [QgsWkbTypes.PointGeometry] + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + massnahme = Column(Enum(XP_ABEMassnahmenTypen)) + gegenstand = Column(Enum(XP_AnpflanzungBindungErhaltungsGegenstand)) + kronendurchmesser = Column(Length) + pflanztiefe = Column(Length) + istAusgleich = Column(Boolean) + baumArt = XPCol(String, version=XPlanVersion.FIVE_THREE) + pflanzenArt = XPCol(String, version=XPlanVersion.SIX) + mindesthoehe = Column(Length) + anzahl = Column(Integer) + + __icon_map__ = [ + ('Anpflanzen: Bäume', '"gegenstand" LIKE \'1%\' and "massnahme" LIKE \'2000\'', 'Anpflanzen_Baum.svg'), + ('Anpflanzen: Sträucher/Hecken', '"gegenstand" LIKE \'2%\' and "massnahme" LIKE \'2000\'', 'Anpflanzen_Straeucher.svg'), + ('Anpflanzen: Sonstiges', '"gegenstand" LIKE \'3%\' and "massnahme" LIKE \'2000\'', 'Anpflanzen_Sonstiges.svg'), + ('Erhaltung: Bäume', '"gegenstand" LIKE \'1%\' and "massnahme" LIKE \'1000\'', 'Erhaltung_Baum.svg'), + ('Erhaltung: Sträucher/Hecken', '"gegenstand" LIKE \'2%\' and "massnahme" LIKE \'1000\'', 'Erhaltung_Straeucher.svg'), + ('Erhaltung: Sonstiges', '"gegenstand" LIKE \'3%\' and "massnahme" LIKE \'1000\'', 'Erhaltung_Sonstiges.svg'), + ] + + def layer_fields(self): + return { + 'massnahme': self.massnahme.value if self.massnahme else '', + 'gegenstand': self.gegenstand.value if self.gegenstand else '', + 'skalierung': self.skalierung if self.skalierung else '', + 'drehwinkel': self.drehwinkel if self.drehwinkel else '' + } + + @classmethod + def attributes(cls): + return ['massnahme', 'gegenstand', 'skalierung', 'drehwinkel'] + + @classmethod + def point_symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PointGeometry) + return symbol + + @classmethod + def polygon_symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + symbol.setClipFeaturesToExtent(False) + + border = QgsSimpleLineSymbolLayer(QColor(0, 0, 0)) + border.setWidth(0.3) + border.setOutputUnit(QgsUnitTypes.RenderMapUnits) + border.setPenJoinStyle(Qt.MiterJoin) + + green_fill = QgsSimpleFillSymbolLayer(QColor('#16ce3c')) + + shape = QgsSimpleMarkerSymbolLayerBase.Circle + circle_symbol = QgsSimpleMarkerSymbolLayer(shape=shape, color=QColor('#000000'), size=1.5) + circle_symbol.setFillColor(QColor(Qt.transparent)) + circle_symbol.setStrokeWidth(0.3) + circle_symbol.setStrokeWidthUnit(QgsUnitTypes.RenderMapUnits) + circle_symbol.setOutputUnit(QgsUnitTypes.RenderMapUnits) + + marker_line = QgsMarkerLineSymbolLayer(interval=3) + marker_line.setOffset(1.5) + marker_line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + marker_symbol = QgsMarkerSymbol() + marker_symbol.deleteSymbolLayer(0) + marker_symbol.appendSymbolLayer(circle_symbol) + marker_line.setSubSymbol(marker_symbol) + + symbol.appendSymbolLayer(border) + symbol.appendSymbolLayer(green_fill) + symbol.appendSymbolLayer(marker_line) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType): + if geom_type == QgsWkbTypes.PointGeometry: + renderer = RuleBasedSymbolRenderer(cls.__icon_map__, cls.point_symbol(), + 'BP_Naturschutz_Landschaftsbild_Naturhaushalt', + geometry_type=QgsWkbTypes.PointGeometry) + return renderer + elif geom_type == QgsWkbTypes.PolygonGeometry: + return QgsSingleSymbolRenderer(cls.polygon_symbol()) + else: + raise NotImplementedError('Liniengeometrien nicht umgesetzt') + + @classmethod + def previewIcon(cls): + return QIcon(os.path.abspath(os.path.join(BASE_DIR, 'symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Baum.svg'))) + + +class BP_SchutzPflegeEntwicklungsFlaeche(PolygonGeometry, BP_Objekt): + """ Umgrenzung von Flächen für Maßnahmen zum Schutz, zur Pflege und zur Entwicklung von Natur und Landschaft + (§9 Abs. 1 Nr. 20 und Abs. 4 BauGB) """ + + __tablename__ = 'bp_schutzflaeche' + __mapper_args__ = { + 'polymorphic_identity': 'bp_schutzflaeche', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + ziel = Column(Enum(XP_SPEZiele)) + sonstZiel = Column(String) + massnahme = relationship("XP_SPEMassnahmenDaten", back_populates="bp_schutzflaeche", cascade="all, delete", + passive_deletes=True) + istAusgleich = Column(Boolean) + refMassnahmenText = relationship("XP_ExterneReferenz", back_populates="bp_schutzflaeche_massnahme", + cascade="all, delete", passive_deletes=True, uselist=False, + foreign_keys='XP_ExterneReferenz.bp_schutzflaeche_massnahme_id') + refLandschaftsplan = relationship("XP_ExterneReferenz", back_populates="bp_schutzflaeche_plan", + cascade="all, delete", passive_deletes=True, uselist=False, + foreign_keys='XP_ExterneReferenz.bp_schutzflaeche_plan_id') + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + symbol.setClipFeaturesToExtent(False) + + border = QgsSimpleLineSymbolLayer(QColor(0, 0, 0)) + border.setWidth(0.5) + border.setOutputUnit(QgsUnitTypes.RenderMapUnits) + border.setPenJoinStyle(Qt.MiterJoin) + + green_strip = QgsSimpleLineSymbolLayer(QColor(22, 206, 60)) + green_strip.setWidth(3) + green_strip.setOffset(1.75) + green_strip.setOutputUnit(QgsUnitTypes.RenderMapUnits) + green_strip.setPenJoinStyle(Qt.MiterJoin) + + symbol.appendSymbolLayer(border) + symbol.appendSymbolLayer(green_strip) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(48, 48)) diff --git a/src/SAGisXPlanung/BPlan/BP_Sonstiges/__init__.py b/src/SAGisXPlanung/BPlan/BP_Sonstiges/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/BPlan/BP_Sonstiges/enums.py b/src/SAGisXPlanung/BPlan/BP_Sonstiges/enums.py new file mode 100644 index 0000000..ac26fcd --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Sonstiges/enums.py @@ -0,0 +1,21 @@ +from enum import Enum + +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin + + +class BP_WegerechtTypen(XPlanungEnumMixin, Enum): + """ Typ des Wegerechts """ + + Gehrecht = 1000 + Fahrrecht = 2000 + Radfahrrecht = 2500 + Leitungsrecht = 4000 + Sonstiges = 9999 + + +class BP_AbgrenzungenTypen(XPlanungEnumMixin, Enum): + """ Typ der Abgrenzung """ + + Nutzungsartengrenze = 1000 + UnterschiedlicheHoehen = 2000 + SonstigeAbgrenzung = 9999 diff --git a/src/SAGisXPlanung/BPlan/BP_Sonstiges/feature_types.py b/src/SAGisXPlanung/BPlan/BP_Sonstiges/feature_types.py new file mode 100644 index 0000000..3b1e87e --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Sonstiges/feature_types.py @@ -0,0 +1,156 @@ +import logging +import os + +from qgis.core import (QgsSymbol, QgsWkbTypes, QgsSingleSymbolRenderer, QgsSimpleFillSymbolLayer, QgsSymbolLayerUtils, + QgsSvgMarkerSymbolLayer, QgsMarkerLineSymbolLayer, QgsMarkerSymbol, QgsUnitTypes, + QgsSimpleLineSymbolLayer, QgsSimpleMarkerSymbolLayer, Qgis, QgsSimpleMarkerSymbolLayerBase) +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import Qt, QSize +from sqlalchemy import Column, ForeignKey, Boolean, String, Enum, ARRAY + +from SAGisXPlanung import BASE_DIR +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Objekt +from SAGisXPlanung.BPlan.BP_Sonstiges.enums import BP_WegerechtTypen, BP_AbgrenzungenTypen +from SAGisXPlanung.XPlan.mixins import PolygonGeometry, FlaechenschlussObjekt, LineGeometry +from SAGisXPlanung.XPlan.types import Length, GeometryType, XPEnum + +logger = logging.getLogger(__name__) + + +class BP_FlaecheOhneFestsetzung(PolygonGeometry, FlaechenschlussObjekt, BP_Objekt): + """ Fläche, für die keine geplante Nutzung angegeben werden kann """ + + __tablename__ = 'bp_flaeche_ohne_festsetzung' + __mapper_args__ = { + 'polymorphic_identity': 'bp_flaeche_ohne_festsetzung', + } + + hidden = True + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer.create({}) + fill.setFillColor(QColor(0, 0, 0)) + fill.setBrushStyle(Qt.BDiagPattern) + + symbol.appendSymbolLayer(fill) + symbol.setOpacity(0.2) + return QgsSingleSymbolRenderer(symbol) + + +class BP_Wegerecht(PolygonGeometry, BP_Objekt): + """ Festsetzung von Flächen, die mit Geh-, Fahr-, und Leitungsrechten zugunsten der Allgemeinheit, eines + Erschließungsträgers, oder eines beschränkten Personenkreises belastet sind (§ 9 Abs. 1 Nr. 21 und Abs. 6 + BauGB). """ + + def __init__(self): + # BP_Wegerecht muss ein Überlagerungsobjekt sein; Konformitätsbedingung 4.14.2.1 + self.flaechenschluss = False + + __readonly_columns__ = ['position', 'flaechenschluss'] + + __tablename__ = 'bp_wegerecht' + __mapper_args__ = { + 'polymorphic_identity': 'bp_wegerecht', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + typ = Column(ARRAY(Enum(BP_WegerechtTypen))) + zugunstenVon = Column(String) + thema = Column(String) + breite = Column(Length) + istSchmal = Column(Boolean) + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + symbol.setClipFeaturesToExtent(False) + + path = os.path.abspath(os.path.join(BASE_DIR, f'symbole/Sonstiges/BP_WegerechtFlaeche.svg')) + svg_symbol_layer = QgsSvgMarkerSymbolLayer(path, size=4) + svg_symbol_layer.setOutputUnit(QgsUnitTypes.RenderMapUnits) + + marker_line = QgsMarkerLineSymbolLayer(interval=5) + marker_line.setOffset(0.8) + marker_line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + marker_symbol = QgsMarkerSymbol() + marker_symbol.deleteSymbolLayer(0) + marker_symbol.appendSymbolLayer(svg_symbol_layer) + marker_line.setSubSymbol(marker_symbol) + + border = QgsSimpleLineSymbolLayer(QColor(0, 0, 0)) + border.setWidth(0.25) + border.setOutputUnit(QgsUnitTypes.RenderMapUnits) + + symbol.appendSymbolLayer(border) + symbol.appendSymbolLayer(marker_line) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(48, 48)) + + @classmethod + def hidden_inputs(cls): + h = super(BP_Wegerecht, cls).hidden_inputs() + return h + ['flaechenschluss'] + + +class BP_NutzungsartenGrenze(LineGeometry, BP_Objekt): + """ Festsetzung einer Baulinie (§9 Abs. 1 Nr. 2 BauGB, §22 und 23 BauNVO). """ + + __tablename__ = 'bp_nutzungsgrenze' + __mapper_args__ = { + 'polymorphic_identity': 'bp_nutzungsgrenze', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + typ = Column(XPEnum(BP_AbgrenzungenTypen, include_default=True)) + + @classmethod + def symbol(cls): + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.LineGeometry) + symbol.deleteSymbolLayer(0) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor('black')) + line.setWidth(0.1) + line.setOutputUnit(QgsUnitTypes.RenderMetersInMapUnits) + + if Qgis.versionInt() >= 32400: + shape = Qgis.MarkerShape.Circle + else: + shape = QgsSimpleMarkerSymbolLayerBase.Circle + dots_symbol_layer = QgsSimpleMarkerSymbolLayer(shape=shape, color=QColor('black'), size=1) + dots_symbol_layer.setOutputUnit(QgsUnitTypes.RenderMetersInMapUnits) + + marker_line = QgsMarkerLineSymbolLayer(interval=3) + marker_line.setOutputUnit(QgsUnitTypes.RenderMetersInMapUnits) + marker_symbol = QgsMarkerSymbol() + marker_symbol.deleteSymbolLayer(0) + marker_symbol.appendSymbolLayer(dots_symbol_layer) + marker_line.setSubSymbol(marker_symbol) + + symbol.appendSymbolLayer(line) + symbol.appendSymbolLayer(marker_line) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(64, 64)) \ No newline at end of file diff --git a/src/SAGisXPlanung/BPlan/BP_Ver_und_Entsorgung/__init__.py b/src/SAGisXPlanung/BPlan/BP_Ver_und_Entsorgung/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/BPlan/BP_Ver_und_Entsorgung/data_types.py b/src/SAGisXPlanung/BPlan/BP_Ver_und_Entsorgung/data_types.py new file mode 100644 index 0000000..3ec96cd --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Ver_und_Entsorgung/data_types.py @@ -0,0 +1,30 @@ +from uuid import uuid4 + +from sqlalchemy import Column, Enum, String, ForeignKey +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from SAGisXPlanung import Base +from SAGisXPlanung.XPlan.enums import XP_ZweckbestimmungVerEntsorgung +from SAGisXPlanung.XPlan.mixins import RelationshipMixin, ElementOrderMixin + + +class BP_KomplexeZweckbestVerEntsorgung(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung einer Versorgungsfläche. """ + + __tablename__ = 'bp_zweckbestimmung_versorgung' + __avoidRelation__ = ['versorgung'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_ZweckbestimmungVerEntsorgung), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + versorgung_id = Column(UUID(as_uuid=True), ForeignKey('bp_versorgung.id', ondelete='CASCADE')) + versorgung = relationship('BP_VerEntsorgung', back_populates='rel_zweckbestimmung') + + @classmethod + def avoid_export(cls): + return ['versorgung_id'] diff --git a/src/SAGisXPlanung/BPlan/BP_Ver_und_Entsorgung/feature_types.py b/src/SAGisXPlanung/BPlan/BP_Ver_und_Entsorgung/feature_types.py new file mode 100644 index 0000000..b63cdc8 --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Ver_und_Entsorgung/feature_types.py @@ -0,0 +1,137 @@ +from typing import List + +from qgis.core import (QgsSymbol, QgsWkbTypes, QgsSymbolLayerUtils, QgsSimpleFillSymbolLayer, QgsSingleSymbolRenderer) +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import QSize + +from sqlalchemy import Integer, Column, ForeignKey, Float, Enum, String +from sqlalchemy.orm import relationship, declared_attr + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Objekt +from SAGisXPlanung.RuleBasedSymbolRenderer import RuleBasedSymbolRenderer +from SAGisXPlanung.XPlan.core import XPCol, XPRelationshipProperty +from SAGisXPlanung.XPlan.enums import XP_ZweckbestimmungVerEntsorgung +from SAGisXPlanung.XPlan.mixins import PolygonGeometry +from SAGisXPlanung.XPlan.types import Area, Length, Volume, GeometryType + + +class BP_VerEntsorgung(PolygonGeometry, BP_Objekt): + """ Flächen und Leitungen für Versorgungsanlagen, für die Abfallentsorgung und Abwasserbeseitigung sowie für + Ablagerungen (§9 Abs. 1, Nr. 12, 14 und Abs. 6 BauGB) """ + + __tablename__ = 'bp_versorgung' + __mapper_args__ = { + 'polymorphic_identity': 'bp_versorgung', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + MaxZahlWohnungen = Column(Integer) + MinGRWohneinheit = Column(Area) + Fmin = Column(Area) + Fmax = Column(Area) + Bmin = Column(Length) + Bmax = Column(Length) + Tmin = Column(Length) + Tmax = Column(Length) + GFZmin = Column(Float) + GFZmax = Column(Float) + GFZ = Column(Float) + GFZ_Ausn = Column(Float) + GFmin = Column(Area) + GFmax = Column(Area) + GF = Column(Area) + GF_Ausn = Column(Area) + BMZ = Column(Float) + BMZ_Ausn = Column(Float) + BM = Column(Volume) + BM_Ausn = Column(Volume) + GRZmin = Column(Float) + GRZmax = Column(Float) + GRZ = Column(Float) + GRZ_Ausn = Column(Float) + GRmin = Column(Area) + GRmax = Column(Area) + GR = Column(Area) + GR_Ausn = Column(Area) + Zmin = Column(Integer) + Zmax = Column(Integer) + Zzwingend = Column(Integer) + Z = Column(Integer) + Z_Ausn = Column(Integer) + Z_Staffel = Column(Integer) + Z_Dach = Column(Integer) + ZUmin = Column(Integer) + ZUmax = Column(Integer) + ZUzwingend = Column(Integer) + ZU = Column(Integer) + ZU_Ausn = Column(Integer) + + @declared_attr + def zweckbestimmung(cls): + return XPCol(Enum(XP_ZweckbestimmungVerEntsorgung), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_zweckbestimmung_attr) + + rel_zweckbestimmung = relationship("BP_KomplexeZweckbestVerEntsorgung", back_populates="versorgung", + cascade="all, delete", passive_deletes=True) + + textlicheErgaenzung = XPCol(String, version=XPlanVersion.FIVE_THREE) + zugunstenVon = Column(String) + + __icon_map__ = [ + ('Elektrizität', '"zweckbestimmung" LIKE \'10%\'', 'Elektrizitaet.svg'), + ('Gas', '"zweckbestimmung" LIKE \'12%\'', 'Gas.svg'), + ('Waermeversorgung', '"zweckbestimmung" LIKE \'14%\'', 'Fernwaerme.svg'), + ('Wasser', '"zweckbestimmung" LIKE \'16%\'', 'Wasser.svg'), + ('Abwasser', '"zweckbestimmung" LIKE \'18%\'', 'Abwasser.svg'), + ('Abfallentsorgung', '"zweckbestimmung" LIKE \'22%\'', 'Abfall.svg'), + ('Ablagerung', '"zweckbestimmung" LIKE \'24%\'', 'Ablagerung.svg'), + ('Erneuerbare Energien', '"zweckbestimmung" LIKE \'2800\'', 'Erneuerbare_Energien.svg'), + ('Kraft-Wärme-Kopplung', '"zweckbestimmung" LIKE \'3000\'', 'Kraft_Waerme_Kopplung.svg'), + ('Sonstiges', '', ''), + ] + + def layer_fields(self): + return { + 'zweckbestimmung': self.zweckbestimmung.value if self.zweckbestimmung else '', + 'skalierung': self.skalierung if self.skalierung else '', + 'drehwinkel': self.drehwinkel if self.drehwinkel else '' + } + + @classmethod + def import_zweckbestimmung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'zweckbestimmung' + else: + return 'rel_zweckbestimmung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_zweckbestimmung', xplan_attribute='zweckbestimmung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def attributes(cls): + return ['zweckbestimmung', 'skalierung', 'drehwinkel'] + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#f7ff5a')) + + symbol.appendSymbolLayer(fill) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + renderer = RuleBasedSymbolRenderer(cls.__icon_map__, cls.symbol(), 'BP_Ver_und_Entsorgung') + return renderer + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) \ No newline at end of file diff --git a/src/SAGisXPlanung/BPlan/BP_Verkehr/__init__.py b/src/SAGisXPlanung/BPlan/BP_Verkehr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/BPlan/BP_Verkehr/enums.py b/src/SAGisXPlanung/BPlan/BP_Verkehr/enums.py new file mode 100644 index 0000000..188bd35 --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Verkehr/enums.py @@ -0,0 +1,50 @@ +from enum import Enum + +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin + + +class BP_ZweckbestimmungStrassenverkehr(XPlanungEnumMixin, Enum): + """ Zweckbestimmungen für Strassenverkehrsflächen""" + + Parkierungsflaeche = 1000 + Fussgaengerbereich = 1100 + VerkehrsberuhigterBereich = 1200 + RadGehweg = 1300 + Radweg = 1400 + Gehweg = 1500 + Wanderweg = 1550 + ReitKutschweg = 1560 + Wirtschaftsweg = 1580 + FahrradAbstellplatz = 1600 + UeberfuehrenderVerkehrsweg = 1700 + UnterfuehrenderVerkehrsweg = 1800 + P_RAnlage = 2000 + Platz = 2100 + Anschlussflaeche = 2200 + LandwirtschaftlicherVerkehr = 2300 + Verkehrsgruen = 2400 + Rastanlage = 2500 + Busbahnhof = 2600 + CarSharing = 3000 + BikeSharing = 3100 + B_RAnlage = 3200 + Parkhaus = 3300 + Mischverkehrsflaeche = 3400 + Ladestation = 3500 + Sonstiges = 9999 + + +class BP_BereichOhneEinAusfahrtTypen (XPlanungEnumMixin, Enum): + """ Typ des Bereiches ohne Ein- und Ausfahrt """ + + KeineEinfahrt = 1000 + KeineAusfahrt = 2000 + KeineEinAusfahrt = 3000 + + +class BP_EinfahrtTypen(XPlanungEnumMixin, Enum): + """ Typ der Einfahrt """ + + Einfahrt = 1000 + Ausfahrt = 2000 + EinAusfahrt = 3000 diff --git a/src/SAGisXPlanung/BPlan/BP_Verkehr/feature_types.py b/src/SAGisXPlanung/BPlan/BP_Verkehr/feature_types.py new file mode 100644 index 0000000..281c39d --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Verkehr/feature_types.py @@ -0,0 +1,321 @@ +from qgis.core import (QgsSymbol, QgsWkbTypes, QgsSingleSymbolRenderer, QgsSimpleLineSymbolLayer, + QgsSymbolLayerUtils, QgsSimpleFillSymbolLayer, QgsUnitTypes, QgsSymbolLayer, + QgsLinePatternFillSymbolLayer, QgsLineSymbol, Qgis, QgsSimpleMarkerSymbolLayerBase, + QgsMarkerLineSymbolLayer, QgsMarkerSymbol, QgsSimpleMarkerSymbolLayer, QgsProperty) +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import QSize + +from sqlalchemy import Integer, Column, ForeignKey, Float, Enum, String + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Objekt +from SAGisXPlanung.BPlan.BP_Verkehr.enums import BP_ZweckbestimmungStrassenverkehr, BP_BereichOhneEinAusfahrtTypen, \ + BP_EinfahrtTypen +from SAGisXPlanung.RuleBasedSymbolRenderer import RuleBasedSymbolRenderer +from SAGisXPlanung.XPlan.core import xp_version +from SAGisXPlanung.XPlan.enums import XP_Nutzungsform +from SAGisXPlanung.XPlan.mixins import PolygonGeometry, LineGeometry, FlaechenschlussObjekt, PointGeometry +from SAGisXPlanung.XPlan.types import Area, Length, Volume, GeometryType, XPEnum + + +@xp_version(versions=[XPlanVersion.FIVE_THREE]) +class BP_StrassenVerkehrsFlaeche(PolygonGeometry, FlaechenschlussObjekt, BP_Objekt): + """ Strassenverkehrsfläche (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB) """ + + __tablename__ = 'bp_strassenverkehr' + __mapper_args__ = { + 'polymorphic_identity': 'bp_strassenverkehr', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + MaxZahlWohnungen = Column(Integer) + MinGRWohneinheit = Column(Area) + Fmin = Column(Area) + Fmax = Column(Area) + Bmin = Column(Length) + Bmax = Column(Length) + Tmin = Column(Length) + Tmax = Column(Length) + GFZmin = Column(Float) + GFZmax = Column(Float) + GFZ = Column(Float) + GFZ_Ausn = Column(Float) + GFmin = Column(Area) + GFmax = Column(Area) + GF = Column(Area) + GF_Ausn = Column(Area) + BMZ = Column(Float) + BMZ_Ausn = Column(Float) + BM = Column(Volume) + BM_Ausn = Column(Volume) + GRZmin = Column(Float) + GRZmax = Column(Float) + GRZ = Column(Float) + GRZ_Ausn = Column(Float) + GRmin = Column(Area) + GRmax = Column(Area) + GR = Column(Area) + GR_Ausn = Column(Area) + Zmin = Column(Integer) + Zmax = Column(Integer) + Zzwingend = Column(Integer) + Z = Column(Integer) + Z_Ausn = Column(Integer) + Z_Staffel = Column(Integer) + Z_Dach = Column(Integer) + ZUmin = Column(Integer) + ZUmax = Column(Integer) + ZUzwingend = Column(Integer) + ZU = Column(Integer) + ZU_Ausn = Column(Integer) + nutzungsform = Column(XPEnum(XP_Nutzungsform, include_default=True)) + # begrenzungslinie [0, *] ? + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#fbdd19')) + + symbol.appendSymbolLayer(fill) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) + + +class BP_StrassenbegrenzungsLinie(LineGeometry, BP_Objekt): + """ Straßenbegrenzungslinie (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB) . """ + + __tablename__ = 'bp_strassenbegrenzung' + __mapper_args__ = { + 'polymorphic_identity': 'bp_strassenbegrenzung', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + bautiefe = Column(Length) + + @classmethod + def symbol(cls): + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.LineGeometry) + symbol.deleteSymbolLayer(0) + + colored_strip = QgsSimpleLineSymbolLayer(QColor('#57e158')) + colored_strip.setWidth(0.7) + colored_strip.setOffset(0.3) + + border = QgsSimpleLineSymbolLayer(QColor(0, 0, 0)) + border.setWidth(0.3) + + symbol.appendSymbolLayer(colored_strip) + symbol.appendSymbolLayer(border) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) + + +@xp_version(versions=[XPlanVersion.FIVE_THREE]) +class BP_VerkehrsflaecheBesondererZweckbestimmung(PolygonGeometry, BP_Objekt): + """ Verkehrsfläche besonderer Zweckbestimmung (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB). """ + + __tablename__ = 'bp_verkehr_besonders' + __mapper_args__ = { + 'polymorphic_identity': 'bp_verkehr_besonders', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + MaxZahlWohnungen = Column(Integer) + MinGRWohneinheit = Column(Area) + Fmin = Column(Area) + Fmax = Column(Area) + Bmin = Column(Length) + Bmax = Column(Length) + Tmin = Column(Length) + Tmax = Column(Length) + GFZmin = Column(Float) + GFZmax = Column(Float) + GFZ = Column(Float) + GFZ_Ausn = Column(Float) + GFmin = Column(Area) + GFmax = Column(Area) + GF = Column(Area) + GF_Ausn = Column(Area) + BMZ = Column(Float) + BMZ_Ausn = Column(Float) + BM = Column(Volume) + BM_Ausn = Column(Volume) + GRZmin = Column(Float) + GRZmax = Column(Float) + GRZ = Column(Float) + GRZ_Ausn = Column(Float) + GRmin = Column(Area) + GRmax = Column(Area) + GR = Column(Area) + GR_Ausn = Column(Area) + Zmin = Column(Integer) + Zmax = Column(Integer) + Zzwingend = Column(Integer) + Z = Column(Integer) + Z_Ausn = Column(Integer) + Z_Staffel = Column(Integer) + Z_Dach = Column(Integer) + ZUmin = Column(Integer) + ZUmax = Column(Integer) + ZUzwingend = Column(Integer) + ZU = Column(Integer) + ZU_Ausn = Column(Integer) + zweckbestimmung = Column(Enum(BP_ZweckbestimmungStrassenverkehr)) + nutzungsform = Column(XPEnum(XP_Nutzungsform, include_default=True)) + # begrenzungslinie [0, *] ? + zugunstenVon = Column(String) + + __icon_map__ = [ + ('Parkplatz', '"zweckbestimmung" LIKE \'1000\'', 'Parkierungsflaeche.svg'), + ('Fußgängerbereich', '"zweckbestimmung" LIKE \'1100\'', 'Fussgaengerbereich.svg'), + ('Verkehrsberuhigte Zone', '"zweckbestimmung" LIKE \'1200\'', 'VerkehrsberuhigterBereich.svg'), + ('Sonstiges', '', ''), + ] + + def layer_fields(self): + return { + 'zweckbestimmung': self.zweckbestimmung.value if self.zweckbestimmung else '', + 'skalierung': self.skalierung if self.skalierung else '', + 'drehwinkel': self.drehwinkel if self.drehwinkel else '' + } + + @classmethod + def attributes(cls): + return ['zweckbestimmung', 'skalierung', 'drehwinkel'] + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#ffffff')) + symbol.appendSymbolLayer(fill) + + line_pattern = QgsLinePatternFillSymbolLayer() + line_pattern_symbol: QgsLineSymbol = QgsLineSymbol.createSimple({}) + line_pattern_symbol.deleteSymbolLayer(0) + line_pattern_layer = QgsSimpleLineSymbolLayer(QColor('#fbdd19')) + line_pattern_layer.setWidth(2) + line_pattern_layer.setWidthUnit(QgsUnitTypes.RenderMapUnits) + line_pattern_symbol.appendSymbolLayer(line_pattern_layer) + line_pattern.setDistanceUnit(QgsUnitTypes.RenderMapUnits) + line_pattern.setSubSymbol(line_pattern_symbol) + symbol.appendSymbolLayer(line_pattern) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + renderer = RuleBasedSymbolRenderer(cls.__icon_map__, cls.symbol(), 'BP_Verkehr') + return renderer + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) + + +class BP_BereichOhneEinAusfahrtLinie(LineGeometry, BP_Objekt): + """ Bereich ohne Ein- und Ausfahrt (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB). """ + + __tablename__ = 'bp_keine_ein_ausfahrt' + __mapper_args__ = { + 'polymorphic_identity': 'bp_keine_ein_ausfahrt', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + typ = Column(XPEnum(BP_BereichOhneEinAusfahrtTypen, include_default=True)) + + @classmethod + def symbol(cls): + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.LineGeometry) + symbol.deleteSymbolLayer(0) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor('black')) + line.setWidth(0.1) + line.setOutputUnit(QgsUnitTypes.RenderMetersInMapUnits) + + if Qgis.versionInt() >= 32400: + shape = Qgis.MarkerShape.SemiCircle + else: + shape = QgsSimpleMarkerSymbolLayerBase.SemiCircle + half_dots_symbol_layer = QgsSimpleMarkerSymbolLayer(shape=shape, color=QColor('black'), size=1) + half_dots_symbol_layer.setOutputUnit(QgsUnitTypes.RenderMetersInMapUnits) + + marker_line = QgsMarkerLineSymbolLayer(interval=3) + marker_line.setOutputUnit(QgsUnitTypes.RenderMetersInMapUnits) + marker_symbol = QgsMarkerSymbol() + marker_symbol.deleteSymbolLayer(0) + marker_symbol.appendSymbolLayer(half_dots_symbol_layer) + marker_line.setSubSymbol(marker_symbol) + + symbol.appendSymbolLayer(line) + symbol.appendSymbolLayer(marker_line) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) + + +class BP_EinfahrtPunkt(PointGeometry, BP_Objekt): + """ Punktförmig abgebildete Einfahrt (§9 Abs. 1 Nr. 11 und Abs. 6 BauGB) """ + + __tablename__ = 'bp_einfahrtpunkt' + __mapper_args__ = { + 'polymorphic_identity': 'bp_einfahrtpunkt', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + typ = Column(XPEnum(BP_EinfahrtTypen, include_default=True)) + + @classmethod + def symbol(cls): + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PointGeometry) + symbol.deleteSymbolLayer(0) + + if Qgis.versionInt() >= 32400: + shape = Qgis.MarkerShape.Triangle + else: + shape = QgsSimpleMarkerSymbolLayerBase.Triangle + triangle_layer = QgsSimpleMarkerSymbolLayer(shape=shape, color=QColor('black'), size=5) + triangle_layer.setOutputUnit(QgsUnitTypes.RenderMapUnits) + + angle_prop = QgsProperty.fromField("nordwinkel") + triangle_layer.setDataDefinedProperty(QgsSymbolLayer.Property.PropertyAngle, angle_prop) + + symbol.appendSymbolLayer(triangle_layer) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) diff --git a/src/SAGisXPlanung/BPlan/BP_Wasser/__init__.py b/src/SAGisXPlanung/BPlan/BP_Wasser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/BPlan/BP_Wasser/feature_types.py b/src/SAGisXPlanung/BPlan/BP_Wasser/feature_types.py new file mode 100644 index 0000000..92cff4a --- /dev/null +++ b/src/SAGisXPlanung/BPlan/BP_Wasser/feature_types.py @@ -0,0 +1,62 @@ +from qgis.core import (QgsSymbol, QgsWkbTypes, QgsSymbolLayerUtils, QgsSimpleFillSymbolLayer) +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import QSize + +from sqlalchemy import Column, ForeignKey, Enum + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Objekt +from SAGisXPlanung.RuleBasedSymbolRenderer import RuleBasedSymbolRenderer +from SAGisXPlanung.XPlan.core import xp_version +from SAGisXPlanung.XPlan.enums import XP_ZweckbestimmungGewaesser +from SAGisXPlanung.XPlan.mixins import PolygonGeometry, FlaechenschlussObjekt +from SAGisXPlanung.XPlan.types import GeometryType + + +@xp_version(versions=[XPlanVersion.FIVE_THREE]) +class BP_GewaesserFlaeche(PolygonGeometry, FlaechenschlussObjekt, BP_Objekt): + """ Festsetzungen von öffentlichen und privaten Grünflächen (§ 9, Abs. 1, Nr. 15 BauGB).""" + + __tablename__ = 'bp_gewaesser' + __mapper_args__ = { + 'polymorphic_identity': 'bp_gewaesser', + } + + id = Column(ForeignKey("bp_objekt.id", ondelete='CASCADE'), primary_key=True) + + zweckbestimmung = Column(Enum(XP_ZweckbestimmungGewaesser)) + + __icon_map__ = [ + ('Hafen/Sportboothafen', '"zweckbestimmung" LIKE \'10%\'', 'Hafen.svg'), + ('Sonstiges Gewässer', '"zweckbestimmung" LIKE \'\'', ''), + ] + + def layer_fields(self): + return { + 'zweckbestimmung': self.zweckbestimmung.value if self.zweckbestimmung else '', + 'skalierung': self.skalierung if self.skalierung else '', + 'drehwinkel': self.drehwinkel if self.drehwinkel else '' + } + + @classmethod + def attributes(cls): + return ['zweckbestimmung', 'skalierung', 'drehwinkel'] + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#c1dfea')) + symbol.appendSymbolLayer(fill) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + renderer = RuleBasedSymbolRenderer(cls.__icon_map__, cls.symbol(), 'BP_Wasser', symbol_size=20) + return renderer + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(16, 16)) \ No newline at end of file diff --git a/src/SAGisXPlanung/BPlan/__init__.py b/src/SAGisXPlanung/BPlan/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/BuildingTemplateItem.py b/src/SAGisXPlanung/BuildingTemplateItem.py new file mode 100644 index 0000000..72ef728 --- /dev/null +++ b/src/SAGisXPlanung/BuildingTemplateItem.py @@ -0,0 +1,320 @@ +from enum import Enum + +from qgis.gui import QgsMapCanvasItem, QgsMapCanvas +from qgis.PyQt.QtWidgets import QGraphicsItem +from qgis.PyQt.QtGui import QPen, QBrush, QPainterPath, QColor, QTransform, QPainter +from qgis.PyQt.QtCore import QPointF, QRectF, QRect, pyqtSlot, QEvent, QObject, Qt, pyqtSignal +from qgis.core import (QgsPointXY, QgsRenderContext, QgsUnitTypes, QgsTextRenderer, QgsTextFormat) + +from SAGisXPlanung.BPlan.BP_Bebauung.enums import BP_Bauweise, BP_BebauungsArt, BP_Dachform +from SAGisXPlanung.XPlan.enums import XP_AllgArtDerBaulNutzung, XP_BesondereArtDerBaulNutzung +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.ext.roman import to_roman + + +class BuildingTemplateItem(QgsMapCanvasItem): + """ Dekoriert Punkt mit Nutzungsschablone """ + + xtype = 'XP_Nutzungsschablone' + + _path = None + _color = QColor('black') + _center = None + + def __init__(self, canvas: QgsMapCanvas, center: QgsPointXY, rows: int, data, parent: XPlanungItem, + scale=0.5, angle=0): + super().__init__(canvas) + self.canvas = canvas + self.data = data + self._center = center + self._scale = scale + self._angle = angle + self.parent = parent + + self.setFlag(QGraphicsItem.ItemIgnoresTransformations) + settings = self.canvas.mapSettings() + self.context = QgsRenderContext.fromMapSettings(settings) + self.event_filter = CanvasEventFilter(self) + + self.columns = 2 + self.rows = rows + self.cell_width = 30 + self.cell_height = 16 + self.width = self.cell_width * 2 + self.height = self.cell_height * self.rows + + self.updatePath() + self.setCenter(center) + self.setRotation(self._angle) + self.setScale(self._scale) + + self.updateCanvas() + + def setItemData(self, data): + self.data = data + + def paint(self, painter, option=None, widget=None): + settings = self.canvas.mapSettings() + self.context = QgsRenderContext.fromMapSettings(settings) + self.context.setPainter(painter) + + painter.setRenderHint(QPainter.Antialiasing) + + pen = QPen(self._color) + pen.setWidth(1) + brush = QBrush(self._color) + painter.setBrush(brush) + + self.updatePath() + painter.strokePath(self._path, painter.pen()) + + self.paintTextCells() + + def paintTextCells(self): + width = self.context.convertFromMapUnits(self.width, QgsUnitTypes.RenderMillimeters) + height = self.context.convertFromMapUnits(self.height, QgsUnitTypes.RenderMillimeters) + cell_width = self.context.convertFromMapUnits(self.cell_width, QgsUnitTypes.RenderMillimeters) + cell_height = self.context.convertFromMapUnits(self.cell_height, QgsUnitTypes.RenderMillimeters) + + for i in range(self.rows): + for j in range(self.columns): + rect = QRectF((j-1)*cell_width, -height / 2 + i*cell_height, cell_width, cell_height) + + index = i * self.columns + j % self.columns + data = self.data[index] + data.text_format.setSizeUnit(QgsUnitTypes.RenderMillimeters) + data.text_format.setSize(cell_height * 0.15) + QgsTextRenderer().drawText(rect, 0, QgsTextRenderer.AlignCenter, [data.text], self.context, + data.text_format, True, QgsTextRenderer.AlignVCenter) + + def beginMove(self): + self.canvas.viewport().installEventFilter(self.event_filter) + + def endMove(self): + self.canvas.viewport().removeEventFilter(self.event_filter) + + def setCenter(self, point: QgsPointXY): + self._center = point + pt = self.toCanvasCoordinates(self._center) + self.setPos(pt) + + def setRowCount(self, row_count: int): + self.rows = row_count + self.height = self.cell_height * self.rows + # updateCanvas() is not enough here, because the extent of the item changes + # therefore do a expensive canvas refresh once + self.canvas.refresh() + + def setAngle(self, angle: int): + self._angle = angle + self.setRotation(self._angle) + + def setScale(self, scale: float): + super(BuildingTemplateItem, self).setScale(scale * 2.0) + self._scale = scale + + def updatePath(self): + self._path = QPainterPath() + + height = self.context.convertFromMapUnits(self.height, QgsUnitTypes.RenderMillimeters) + width = self.context.convertFromMapUnits(self.width, QgsUnitTypes.RenderMillimeters) + + top_left = QPointF(-width/2, -height/2) + + for i in range(self.rows - 1): + self._path.moveTo(QPointF(top_left.x(), top_left.y() + (i+1) * height/self.rows)) + self._path.lineTo(QPointF(top_left.x() + width, top_left.y() + (i+1) * height/self.rows)) + + # vertical bar + self._path.moveTo(QPointF(0, height/2)) + self._path.lineTo(QPointF(0, -height/2)) + + # box + self._path.addRect(top_left.x(), top_left.y(), width, height) + + def updatePosition(self): + self.setCenter(self._center) + + def boundingRect(self): + return self._path.boundingRect() + + def center(self) -> QgsPointXY: + return self._center + + +class CanvasEventFilter(QObject): + # add signal here, because QGraphicsItems dont inherit from QObject and therefore cant emit any signals themselves! + positionUpdated = pyqtSignal(QgsPointXY) + + def __init__(self, canvas_item, parent=None): + self.canvas_item = canvas_item + super(CanvasEventFilter, self).__init__(parent) + + def eventFilter(self, obj, event): + # on mouse move let canvas item follow mouse position + if event.type() == QEvent.MouseMove: + point = self.canvas_item.toMapCoordinates(event.pos()) + self.canvas_item.setCenter(point) + # on click dont propagate the event and finish moving the canvas item + if event.type() == QEvent.MouseButtonRelease: + if event.button() == Qt.MiddleButton: + return False + if event.button() == Qt.LeftButton: + self.canvas_item.endMove() + self.positionUpdated.emit(self.canvas_item.center()) + return True + return False + + +class BuildingTemplateCellFormat(Enum): + Text = 1 + Symbol = 2 + + +class BuildingTemplateCellDataType(Enum): + ArtDerBaulNutzung = 'Art d. baulichen Nutzung' + ZahlVollgeschosse = 'Anzahl der Vollgeschosse' + GRZ = 'Grundflächenzahl' + GFZ = 'Geschossflächenzahl' + BebauungsArt = 'Art der Bebauung' + Bauweise = 'Bauweise' + Dachneigung = 'Dachneigung' + Dachform = 'zulässige Dachform' + + @classmethod + def as_default(cls, rows=3): + default = [cls.ArtDerBaulNutzung, cls.ZahlVollgeschosse, cls.GRZ, cls.GFZ, cls.BebauungsArt, cls.Bauweise] + + if rows == 4: + default += [cls.Dachneigung, cls.Dachform] + + return default + + +class BuildingTemplateData: + + nutzungsArten = { + XP_AllgArtDerBaulNutzung.WohnBauflaeche: 'W', + XP_AllgArtDerBaulNutzung.GemischteBauflaeche: 'M', + XP_AllgArtDerBaulNutzung.GewerblicheBauflaeche: 'G', + XP_AllgArtDerBaulNutzung.SonderBauflaeche: 'S', + } + + spezNutzungsArten = { + XP_BesondereArtDerBaulNutzung.Kleinsiedlungsgebiet: 'S', + XP_BesondereArtDerBaulNutzung.ReinesWohngebiet: 'R', + XP_BesondereArtDerBaulNutzung.AllgWohngebiet: 'A', + XP_BesondereArtDerBaulNutzung.BesonderesWohngebiet: 'B', + XP_BesondereArtDerBaulNutzung.Dorfgebiet: 'D', + XP_BesondereArtDerBaulNutzung.Mischgebiet: 'I', + XP_BesondereArtDerBaulNutzung.UrbanesGebiet: 'U', + XP_BesondereArtDerBaulNutzung.Kerngebiet: 'K', + XP_BesondereArtDerBaulNutzung.Gewerbegebiet: 'E', + XP_BesondereArtDerBaulNutzung.Industriegebiet: 'I', + XP_BesondereArtDerBaulNutzung.Wochenendhausgebiet: 'O\r WOCH', + XP_BesondereArtDerBaulNutzung.Sondergebiet: 'O', + XP_BesondereArtDerBaulNutzung.SondergebietSonst: 'O', + XP_BesondereArtDerBaulNutzung.SondergebietErholung: '0', + XP_BesondereArtDerBaulNutzung.SonstigesGebiet: '' + } + + bauweise = { + BP_Bauweise.OffeneBauweise: 'o', + BP_Bauweise.GeschlosseneBauweise: 'g', + BP_Bauweise.AbweichendeBauweise: 'a', + BP_Bauweise.KeineAngabe: '', + } + + bebauungsart = { + BP_BebauungsArt.Einzelhaeuser: 'E', + BP_BebauungsArt.EinzelDoppelhaeuser: 'E, D', + BP_BebauungsArt.EinzelhaeuserHausgruppen: 'E, G', + BP_BebauungsArt.Doppelhaeuser: 'D', + BP_BebauungsArt.DoppelhaeuserHausgruppen: 'D, G', + BP_BebauungsArt.Hausgruppen: 'G', + BP_BebauungsArt.Reihenhaeuser: 'R', + BP_BebauungsArt.EinzelhaeuserDoppelhaeuserHausgruppen: 'E, D, G' + } + + dachform = { + BP_Dachform.Flachdach: 'FD', + BP_Dachform.Pultdach: 'PD', + BP_Dachform.VersetztesPultdach: 'VPD', + BP_Dachform.GeneigtesDach: 'GD', + BP_Dachform.Satteldach: 'SD', + BP_Dachform.Walmdach: 'WD', + BP_Dachform.KrueppelWalmdach: 'KWD', + BP_Dachform.Mansarddach: 'MD', + BP_Dachform.Zeltdach: 'ZD', + BP_Dachform.Kegeldach: 'KeD', + BP_Dachform.Kuppeldach: 'KuD', + BP_Dachform.Sheddach: 'ShD', + BP_Dachform.Bogendach: 'BD', + BP_Dachform.Turmdach: 'TuD', + BP_Dachform.Tonnendach: 'ToD', + BP_Dachform.Mischform: 'GDF', + BP_Dachform.Sonstiges: 'SDF', + } + + def __init__(self, cell_type: BuildingTemplateCellFormat, text: str = '', + text_format: QgsTextFormat = QgsTextFormat()): + self.cell_type = cell_type + self.text = text + self.text_format = text_format + + @staticmethod + def fromAttribute(item, cell_type, item2=None): + if not item: + return BuildingTemplateData(BuildingTemplateCellFormat.Text) + + if cell_type == BuildingTemplateCellDataType.ArtDerBaulNutzung: + text = BuildingTemplateData.nutzungsArten[item] + if isinstance(item2, XP_BesondereArtDerBaulNutzung): + text += BuildingTemplateData.spezNutzungsArten[item2] + return BuildingTemplateData(BuildingTemplateCellFormat.Text, text) + + if cell_type == BuildingTemplateCellDataType.ZahlVollgeschosse: + text = to_roman(int(item)) + return BuildingTemplateData(BuildingTemplateCellFormat.Text, text) + + if cell_type == BuildingTemplateCellDataType.GRZ: + return BuildingTemplateData(BuildingTemplateCellFormat.Text, str(item)) + + if cell_type == BuildingTemplateCellDataType.GFZ: + text = str(item) + text_format = QgsTextFormat() + # background = QgsTextBackgroundSettings() + # background.setEnabled(True) + # background.setType(QgsTextBackgroundSettings.ShapeCircle) + # + # # fill_symbol = background.fillSymbol() + # fill_symbol = QgsFillSymbol() + # print(fill_symbol.dump()) + # fill_symbol.deleteSymbolLayer(0) + # line = QgsSimpleLineSymbolLayer(QColor(0, 0, 0), 0.1) + # fill_symbol.appendSymbolLayer(line) + # print(fill_symbol.dump()) + # background.setFillSymbol(fill_symbol) + # + # text_format.setBackground(background) + return BuildingTemplateData(BuildingTemplateCellFormat.Text, text, text_format) + + if cell_type == BuildingTemplateCellDataType.BebauungsArt: + text = BuildingTemplateData.bebauungsart[item] + return BuildingTemplateData(BuildingTemplateCellFormat.Text, text) + + if cell_type == BuildingTemplateCellDataType.Bauweise: + text = BuildingTemplateData.bauweise[item] + return BuildingTemplateData(BuildingTemplateCellFormat.Text, text) + + if cell_type == BuildingTemplateCellDataType.Dachneigung: + if item is not None: + return BuildingTemplateData(BuildingTemplateCellFormat.Text, f'{item}°') + elif isinstance(item2, tuple) and item2[0] is not None and item2[1] is not None: + return BuildingTemplateData(BuildingTemplateCellFormat.Text, f'{item2[0]}-{item2[1]}°') + else: + return BuildingTemplateData(BuildingTemplateCellFormat.Text, '') + + if cell_type == BuildingTemplateCellDataType.Dachform: + text = BuildingTemplateData.dachform[item] + return BuildingTemplateData(BuildingTemplateCellFormat.Text, text) diff --git a/src/SAGisXPlanung/ConverterTasks.py b/src/SAGisXPlanung/ConverterTasks.py new file mode 100644 index 0000000..752710e --- /dev/null +++ b/src/SAGisXPlanung/ConverterTasks.py @@ -0,0 +1,70 @@ +import logging +from pathlib import PurePath +from typing import Callable, Tuple +from zipfile import ZipFile + +from sqlalchemy.orm import joinedload + +from SAGisXPlanung import Session +from SAGisXPlanung.GML.GMLReader import GMLReader +from SAGisXPlanung.GML.GMLWriter import GMLWriter +from SAGisXPlanung.Settings import Settings +from SAGisXPlanung.XPlan.feature_types import XP_Plan +from SAGisXPlanung.config import export_version + +logger = logging.getLogger(__name__) + + +def export_plan(out_file_format: str, export_filepath: str, plan_name: str): + with Session.begin() as session: + plan = session.query(XP_Plan).filter(XP_Plan.name == plan_name).first() + writer = GMLWriter(plan, version=export_version()) + + if out_file_format == "gml": + gml = writer.toGML() + with open(export_filepath, 'wb') as f: + f.write(gml) + elif out_file_format == "zip": + archive = writer.toArchive() + with open(export_filepath, 'wb') as f: + f.write(archive.getvalue()) + + +def import_plan(filepath: str, progress_callback: Callable[[Tuple[int, int]], None]) -> str: + extension = PurePath(filepath).suffix + files = {} + + # read contents of gml file + if extension == '.gml': + with open(filepath, 'rb') as f: + gml_file_content = f.read() + + # extract gml and references from zip archive + elif extension == '.zip': + archive = ZipFile(filepath, mode='r') + if not archive.namelist(): + raise ValueError('ZIP-Archiv enthält keine Dateien.') + if not any(PurePath(file_name).suffix == '.gml' for file_name in archive.namelist()): + raise ValueError('ZIP-Archiv enthält keine XPlanGML Datei.') + gml_file_index = next(i for i, name in enumerate(archive.namelist()) if PurePath(name).suffix == '.gml') + gml_file_content = archive.read(archive.namelist()[gml_file_index]) + + files = {file: archive.read(file) for i, file in enumerate(archive.namelist()) if i != gml_file_index} + else: + raise ValueError('Dateipfad muss mit .gml oder .zip enden.') + + reader = GMLReader(gml_file_content, files=files, progress_callback=progress_callback) + + if reader.exeptions: + raise Exception('\n\n '.join([str(exc) for exc in reader.exeptions])) + + plan_name = reader.plan.name + + save_plan(reader.plan) + + return plan_name + + +def save_plan(plan): + with Session.begin() as session: + session.add(plan) diff --git a/src/SAGisXPlanung/FPlan/FP_Basisobjekte/__init__.py b/src/SAGisXPlanung/FPlan/FP_Basisobjekte/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/FPlan/FP_Basisobjekte/enums.py b/src/SAGisXPlanung/FPlan/FP_Basisobjekte/enums.py new file mode 100644 index 0000000..20564b4 --- /dev/null +++ b/src/SAGisXPlanung/FPlan/FP_Basisobjekte/enums.py @@ -0,0 +1,78 @@ +from enum import Enum + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin + + +class FP_PlanArt(XPlanungEnumMixin, Enum): + """ Typ des FPlans """ + + FPlan = 1000 + GemeinsamerFPlan = 2000 + RegFPlan = 3000 + FPlanRegPlan = 4000 + SachlicherTeilplan = 8000 + Sonstiges = 9999 + + +class FP_Verfahren(XPlanungEnumMixin, Enum): + """ Verfahren nach dem ein FPlan aufgestellt oder geändert wird. """ + + Normal = 1000 + Parag13 = 2000 + + +class FP_Rechtsstand(XPlanungEnumMixin, Enum): + """ Aktueller Rechtsstand des Plans. """ + + def __new__(cls, *args, **kwds): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: int, version: XPlanVersion = None): + self._xplan_version = version + + @property + def version(self) -> XPlanVersion: + return self._xplan_version + + Aufstellungsbeschluss = 1000 + Entwurf = 2000 + FruehzeitigeBehoerdenBeteiligung = 2100 + FruehzeitigeOeffentlichkeitsBeteiligung = 2200 + Entwurfsbeschluss = 2250, XPlanVersion.SIX + BehoerdenBeteiligung = 2300 + OeffentlicheAuslegung = 2400 + Plan = 3000 + Wirksamkeit = 4000 + Untergegangen = 5000 + Aufgehoben = 50000 + AusserKraft = 50001 + + +class FP_Rechtscharakter(XPlanungEnumMixin, Enum): + """ Rechtliche Charakterisierung des Planinhaltes """ + + Darstellung = 1000 + NachrichtlicheUebernahme = 2000 + Hinweis = 3000 + Vermerk = 4000 + Kennzeichnung = 5000 + Unbekannt = 9998 + + def to_xp_rechtscharakter(self): + from SAGisXPlanung.XPlan.enums import XP_Rechtscharakter + + if self.value == 1000: + return XP_Rechtscharakter.DarstellungFPlan + elif self.value == 2000: + return XP_Rechtscharakter.NachrichtlicheUebernahme + elif self.value == 3000: + return XP_Rechtscharakter.Hinweis + elif self.value == 4000: + return XP_Rechtscharakter.Vermerk + elif self.value == 5000: + return XP_Rechtscharakter.Kennzeichnung + elif self.value == 9998: + return XP_Rechtscharakter.Unbekannt diff --git a/src/SAGisXPlanung/FPlan/FP_Basisobjekte/feature_types.py b/src/SAGisXPlanung/FPlan/FP_Basisobjekte/feature_types.py new file mode 100644 index 0000000..424f50e --- /dev/null +++ b/src/SAGisXPlanung/FPlan/FP_Basisobjekte/feature_types.py @@ -0,0 +1,167 @@ +from qgis.PyQt.QtCore import Qt +from qgis.PyQt.QtGui import QColor +from qgis.core import (QgsSymbol, QgsWkbTypes, QgsSimpleLineSymbolLayer, QgsSingleSymbolRenderer, + QgsGeometry, QgsCoordinateReferenceSystem) + +from sqlalchemy import Column, ForeignKey, Enum, String, Date, ARRAY, event, Boolean, types +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import relationship +from geoalchemy2 import WKBElement, Geometry, WKTElement + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.FPlan.FP_Basisobjekte.enums import FP_PlanArt, FP_Verfahren, FP_Rechtsstand, FP_Rechtscharakter +from SAGisXPlanung.GML.geometry import geometry_from_spatial_element +from SAGisXPlanung.XPlan.conversions import FP_Rechtscharakter_EnumType +from SAGisXPlanung.XPlan.core import XPCol +from SAGisXPlanung.XPlan.data_types import XP_PlanXP_GemeindeAssoc +from SAGisXPlanung.XPlan.enums import XP_Rechtscharakter +from SAGisXPlanung.XPlan.feature_types import XP_Plan, XP_Bereich, XP_Objekt +from SAGisXPlanung.XPlan.types import GeometryType +from SAGisXPlanung.config import export_version + + +class FP_Plan(XP_Plan): + """ Klasse zur Modellierung eines gesamten Flächennutzungsplans """ + + def __init__(self): + self.auslegungsEndDatum = [] + self.auslegungsStartDatum = [] + self.traegerbeteiligungsStartDatum = [] + self.traegerbeteiligungsEndDatum = [] + + __tablename__ = 'fp_plan' + __requiredRelationships__ = ["gemeinde"] + __mapper_args__ = { + 'polymorphic_identity': 'fp_plan', + } + + id = Column(ForeignKey("xp_plan.id", ondelete='CASCADE'), primary_key=True) + + gemeinde = relationship("XP_Gemeinde", back_populates="fp_plans", secondary=XP_PlanXP_GemeindeAssoc, doc='Gemeinde') + + plangeber_id = Column(UUID(as_uuid=True), ForeignKey('xp_plangeber.id')) + plangeber = relationship("XP_Plangeber", back_populates="fp_plans", doc='Plangeber') + + planArt = Column(Enum(FP_PlanArt), nullable=False, doc='Art des Plans') + # sonstPlanArt: FP_SonstPlanArt[0..1] + sachgebiet = Column(String(), doc='Sachgebiet') + verfahren = Column(Enum(FP_Verfahren), doc='Verfahren') + rechtsstand = Column(Enum(FP_Rechtsstand), doc='Rechtsstand') + # status: FP_Status[0..1] + aufstellungsbeschlussDatum = Column(Date(), doc='Aufstellungsbeschlussdatum') + auslegungsStartDatum = Column(ARRAY(Date), doc='Startdatum der Auslegung') + auslegungsEndDatum = Column(ARRAY(Date), doc='Enddatum der Auslegung') + traegerbeteiligungsStartDatum = Column(ARRAY(Date), doc='Start der Trägerbeteiligung') + traegerbeteiligungsEndDatum = Column(ARRAY(Date), doc='Ende der Trägerbeteiligung') + aenderungenBisDatum = Column(Date(), doc='Änderung bis') + entwurfsbeschlussDatum = Column(Date(), doc='Datum des Entwurfsbeschluss') + planbeschlussDatum = Column(Date(), doc='Datum des Planbeschlusses') + wirksamkeitsDatum = Column(Date(), doc='Datum der Wirksamkeit') + + versionBauNVODatum = XPCol(Date(), doc='Datum der BauNVO', version=XPlanVersion.FIVE_THREE) + versionBauNVOText = XPCol(String(), doc='Textl. Spezifikation der BauNVO', version=XPlanVersion.FIVE_THREE) + versionBauGBDatum = XPCol(Date(), doc='Datum des BauGB', version=XPlanVersion.FIVE_THREE) + versionBauGBText = XPCol(String(), doc='Textl. Spezifikation des BauGB', version=XPlanVersion.FIVE_THREE) + versionSonstRechtsgrundlageDatum = XPCol(Date(), doc='Datum sonst. Rechtsgrundlage', version=XPlanVersion.FIVE_THREE) + versionSonstRechtsgrundlageText = XPCol(String(), doc='Textl. Spezifikation sonst. Rechtsgrundlage', version=XPlanVersion.FIVE_THREE) + + versionBauNVO_id = XPCol(UUID(as_uuid=True), ForeignKey('xp_gesetzliche_grundlage.id'), + version=XPlanVersion.SIX, attribute='versionBauNVO') + versionBauNVO = relationship("XP_GesetzlicheGrundlage", back_populates="fp_bau_nvo", foreign_keys=[versionBauNVO_id]) + versionBauGB_id = XPCol(UUID(as_uuid=True), ForeignKey('xp_gesetzliche_grundlage.id'), + version=XPlanVersion.SIX, attribute='versionBauGB') + versionBauGB = relationship("XP_GesetzlicheGrundlage", back_populates="fp_bau_gb", foreign_keys=[versionBauGB_id]) + versionSonstRechtsgrundlage_id = XPCol(UUID(as_uuid=True), ForeignKey('xp_gesetzliche_grundlage.id'), + version=XPlanVersion.SIX, attribute='versionSonstRechtsgrundlage') + versionSonstRechtsgrundlage = relationship("XP_GesetzlicheGrundlage", back_populates="fp_bau_sonst", + foreign_keys=[versionSonstRechtsgrundlage_id]) + + bereich = relationship("FP_Bereich", back_populates="gehoertZuPlan", cascade="all, delete", doc='Bereich') + + @classmethod + def avoid_export(cls): + return ['plangeber_id', 'versionBauNVO_id', 'versionBauGB_id', 'versionSonstRechtsgrundlage_id'] + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.8) + line.setPenStyle(Qt.SolidLine) + + symbol.appendSymbolLayer(line) + return QgsSingleSymbolRenderer(symbol) + + +@event.listens_for(FP_Plan, 'before_insert') +@event.listens_for(FP_Plan, 'before_update') +def checkIntegrity(mapper, connection, xp_plan): + if not xp_plan.gemeinde: + raise IntegrityError("Planwerk benötigt mindestens eine Gemeinde", None, xp_plan) + if not xp_plan.raeumlicherGeltungsbereich: + raise IntegrityError('Planwerk benötigt mindestens einen Geltungsbereich!', None, xp_plan) + + +class FP_Bereich(XP_Bereich): + """ Diese Klasse modelliert einen Bereich eines Flächennutzungsplans """ + + __tablename__ = 'fp_bereich' + __mapper_args__ = { + 'polymorphic_identity': 'fp_bereich', + } + + id = Column(ForeignKey("xp_bereich.id", ondelete='CASCADE'), primary_key=True) + + gehoertZuPlan_id = Column(UUID(as_uuid=True), ForeignKey('fp_plan.id', ondelete='CASCADE')) + gehoertZuPlan = relationship('FP_Plan', back_populates='bereich') + + +class FP_Objekt(XP_Objekt): + """ Basisklasse für alle Fachobjekte des Flächennutzungsplans """ + + __tablename__ = 'fp_objekt' + __mapper_args__ = { + 'polymorphic_identity': 'fp_objekt', + } + __readonly_columns__ = ['position'] + + id = Column(ForeignKey("xp_objekt.id", ondelete='CASCADE'), primary_key=True) + + rechtscharakter = XPCol(FP_Rechtscharakter_EnumType(FP_Rechtscharakter), nullable=False, doc='Rechtscharakter', + version=XPlanVersion.FIVE_THREE) + vonGenehmigungAusgenommen = Column(Boolean) + + position = Column(Geometry()) + flaechenschluss = Column(Boolean, doc='Flächenschluss') + + def __getattribute__(self, name): + if name == 'rechtscharakter' and export_version() == XPlanVersion.FIVE_THREE: + super_value = super().__getattribute__(name) + if isinstance(super_value, XP_Rechtscharakter): + return super_value.to_fp_rechtscharakter() + return super_value + else: + return super().__getattribute__(name) + + def srs(self): + return QgsCoordinateReferenceSystem(f'EPSG:{self.position.srid}') + + def geometry(self): + return geometry_from_spatial_element(self.position) + + def setGeometry(self, geom: QgsGeometry, srid: int = None): + if srid is None and self.position is None: + raise Exception('geometry needs a srid') + self.position = WKTElement(geom.asWkt(), srid=srid or self.position.srid) + + def geomType(self) -> GeometryType: + return self.geometry().type() + + @classmethod + def hidden_inputs(cls): + h = super(FP_Objekt, cls).hidden_inputs() + return h + ['position'] \ No newline at end of file diff --git a/src/SAGisXPlanung/FPlan/FP_Bebauung/__init__.py b/src/SAGisXPlanung/FPlan/FP_Bebauung/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/FPlan/FP_Bebauung/data_types.py b/src/SAGisXPlanung/FPlan/FP_Bebauung/data_types.py new file mode 100644 index 0000000..946469d --- /dev/null +++ b/src/SAGisXPlanung/FPlan/FP_Bebauung/data_types.py @@ -0,0 +1,30 @@ +from uuid import uuid4 + +from sqlalchemy import Column, Enum, String, ForeignKey +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from SAGisXPlanung import Base +from SAGisXPlanung.XPlan.enums import XP_Sondernutzungen +from SAGisXPlanung.XPlan.mixins import RelationshipMixin, ElementOrderMixin + + +class FP_KomplexeSondernutzung(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation einer Sondernutzung. """ + + __tablename__ = 'fp_komplexe_sondernutzung' + __avoidRelation__ = ['baugebiet'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_Sondernutzungen), nullable=False) + + nutzungText = Column(String) + aufschrift = Column(String) + + baugebiet_id = Column(UUID(as_uuid=True), ForeignKey('fp_baugebiet.id', ondelete='CASCADE')) + baugebiet = relationship('FP_BebauungsFlaeche', back_populates='rel_sondernutzung') + + @classmethod + def avoid_export(cls): + return ['baugebiet_id'] diff --git a/src/SAGisXPlanung/FPlan/FP_Bebauung/feature_types.py b/src/SAGisXPlanung/FPlan/FP_Bebauung/feature_types.py new file mode 100644 index 0000000..e60a542 --- /dev/null +++ b/src/SAGisXPlanung/FPlan/FP_Bebauung/feature_types.py @@ -0,0 +1,184 @@ +import logging +import os +from typing import List + +from qgis.core import (QgsSymbol, QgsWkbTypes, QgsPointXY, QgsGeometry, QgsSingleSymbolRenderer, + QgsSimpleLineSymbolLayer, QgsLimitedRandomColorRamp, QgsRuleBasedRenderer, QgsSymbolLayerUtils, + QgsSimpleFillSymbolLayer, QgsUnitTypes) +from qgis.PyQt.QtGui import QColor, QIcon +from qgis.PyQt.QtCore import Qt + +from sqlalchemy import Column, ForeignKey, Float, Enum, String, ARRAY +from sqlalchemy.orm import declared_attr, relationship + +from SAGisXPlanung import BASE_DIR, XPlanVersion +from SAGisXPlanung.FPlan.FP_Basisobjekte.feature_types import FP_Objekt +from SAGisXPlanung.XPlan.core import XPCol, XPRelationshipProperty +from SAGisXPlanung.XPlan.enums import (XP_AllgArtDerBaulNutzung, XP_BesondereArtDerBaulNutzung, XP_Sondernutzungen, + XP_AbweichungBauNVOTypen) +from SAGisXPlanung.XPlan.mixins import PolygonGeometry, FlaechenschlussObjekt +from SAGisXPlanung.XPlan.types import ConformityException, GeometryType, XPEnum + +logger = logging.getLogger(__name__) + + +class FP_BebauungsFlaeche(PolygonGeometry, FlaechenschlussObjekt, FP_Objekt): + """ Teil eines Baugebiets mit einheitlicher Art der baulichen Nutzung. """ + + __tablename__ = 'fp_baugebiet' + __mapper_args__ = { + 'polymorphic_identity': 'fp_baugebiet', + } + + id = Column(ForeignKey("fp_objekt.id", ondelete='CASCADE'), primary_key=True) + + GFZ = Column(Float) + GFZmin = Column(Float) + GFZmax = Column(Float) + GFZdurchschnittlich = XPCol(Float, version=XPlanVersion.SIX) + BMZ = Column(Float) + GRZ = Column(Float) + allgArtDerBaulNutzung = Column(Enum(XP_AllgArtDerBaulNutzung)) + besondereArtDerBaulNutzung = Column(Enum(XP_BesondereArtDerBaulNutzung)) + + @declared_attr + def sonderNutzung(cls): + return XPCol(ARRAY(Enum(XP_Sondernutzungen)), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_sondernutzung_attr) + + rel_sondernutzung = relationship("FP_KomplexeSondernutzung", back_populates="baugebiet", + cascade="all, delete", passive_deletes=True) + + nutzungText = XPCol(String, version=XPlanVersion.FIVE_THREE) + abweichungBauNVO = XPCol(XPEnum(XP_AbweichungBauNVOTypen, include_default=True), version=XPlanVersion.SIX) + + def layer_fields(self): + return { + 'allgArtDerBaulNutzung': self.allgArtDerBaulNutzung.name if self.allgArtDerBaulNutzung else '' + } + + @classmethod + def import_sondernutzung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'sonderNutzung' + else: + return 'rel_sondernutzung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_sondernutzung', xplan_attribute='sondernutzung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def symbol(cls): + return QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + color_map = [ + ('Wohngebiet', '"allgArtDerBaulNutzung" LIKE \'WohnBauflaeche\'', QColor('#fb6868')), + ('Gemischtes Gebiet', '"allgArtDerBaulNutzung" LIKE \'GemischteBauflaeche\'', QColor('#e48700')), + ('Gewerbliches Gebiet', '"allgArtDerBaulNutzung" LIKE \'GewerblicheBauflaeche\'', QColor('#cfcbcb')), + ('Sondergebiet', '"allgArtDerBaulNutzung" LIKE \'SonderBauflaeche\'', QColor('#f8c85c')), + ('keine Nutzung', '"allgArtDerBaulNutzung" LIKE \'\'', QgsLimitedRandomColorRamp.randomColors(1)[0]) + ] + + renderer = QgsRuleBasedRenderer(cls.symbol()) + root_rule = renderer.rootRule() + + for label, expression, color_name in color_map: + rule = root_rule.children()[0].clone() + rule.setLabel(label) + rule.setFilterExpression(expression) + rule.symbol().setColor(color_name) + root_rule.appendChild(rule) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.3) + line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + line.setPenStyle(Qt.SolidLine) + rule.symbol().appendSymbolLayer(line) + + root_rule.removeChildAt(0) + return renderer + + @classmethod + def previewIcon(cls): + return QIcon(os.path.join(BASE_DIR, 'symbole/BP_BesondererNutzungszweckFlaeche/Allgemeine_Wohngebiete.svg')) + + @classmethod + def attributes(cls): + return ['allgArtDerBaulNutzung'] + + def validate(self): + if (self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Kleinsiedlungsgebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.ReinesWohngebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.AllgWohngebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.BesonderesWohngebiet.name) and ( + self.allgArtDerBaulNutzung != XP_AllgArtDerBaulNutzung.WohnBauflaeche.name): + raise ConformityException(f'Wenn besondereArtDerBaulNutzung den Wert ' + f'{self.besondereArtDerBaulNutzung} hat, ' + f'muss allgArtDerBaulNutzung den Wert ' + f'{XP_AllgArtDerBaulNutzung(1000)} haben', + '5.3.1.1', self.__class__.__name__) + + if (self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Dorfgebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Mischgebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.UrbanesGebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Kerngebiet.name) and ( + self.allgArtDerBaulNutzung != XP_AllgArtDerBaulNutzung.GemischteBauflaeche.name): + raise ConformityException(f'Wenn besondereArtDerBaulNutzung den Wert ' + f'{self.besondereArtDerBaulNutzung} hat, ' + f'muss allgArtDerBaulNutzung den Wert ' + f'{XP_AllgArtDerBaulNutzung(2000)} haben', + '5.3.1.1', self.__class__.__name__) + + if (self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Gewerbegebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Industriegebiet.name) and ( + self.allgArtDerBaulNutzung != XP_AllgArtDerBaulNutzung.GewerblicheBauflaeche.name): + raise ConformityException(f'Wenn besondereArtDerBaulNutzung den Wert ' + f'{self.besondereArtDerBaulNutzung} hat, ' + f'muss allgArtDerBaulNutzung den Wert ' + f'{XP_AllgArtDerBaulNutzung(3000)} haben', + '5.3.1.1', self.__class__.__name__) + + if (self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Sondergebiet.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.SondergebietErholung.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.SondergebietSonst.name or + self.besondereArtDerBaulNutzung == XP_BesondereArtDerBaulNutzung.Wochenendhausgebiet.name) and ( + self.allgArtDerBaulNutzung != XP_AllgArtDerBaulNutzung.SonderBauflaeche.name): + raise ConformityException(f'Wenn besondereArtDerBaulNutzung den Wert ' + f'{self.besondereArtDerBaulNutzung} hat, ' + f'muss allgArtDerBaulNutzung den Wert ' + f'{XP_AllgArtDerBaulNutzung(4000)} haben', + '5.3.1.1', self.__class__.__name__) + + erholung_sondernutzungen = [XP_Sondernutzungen.Wochendhausgebiet.name, XP_Sondernutzungen.Ferienhausgebiet.name, + XP_Sondernutzungen.Campingplatzgebiet.name, XP_Sondernutzungen.Kurgebiet.name, + XP_Sondernutzungen.SonstSondergebietErholung.name] + if (self.sonderNutzung and not set(self.sonderNutzung).isdisjoint(set(erholung_sondernutzungen))) and \ + self.besondereArtDerBaulNutzung != XP_BesondereArtDerBaulNutzung.SondergebietErholung.name: + raise ConformityException(f'Wenn sonderNutzung den Wert {self.sonderNutzung} hat, ' + f'muss besondereArtDerBaulNutzung den Wert ' + f'{XP_BesondereArtDerBaulNutzung(2000)} haben', + '5.3.1.2', self.__class__.__name__) + if (self.sonderNutzung and set(self.sonderNutzung).isdisjoint(set(erholung_sondernutzungen))) and \ + self.besondereArtDerBaulNutzung != XP_BesondereArtDerBaulNutzung.SondergebietSonst.name: + raise ConformityException(f'Wenn sonderNutzung den Wert {self.sonderNutzung} hat, ' + f'muss besondereArtDerBaulNutzung den Wert ' + f'{XP_BesondereArtDerBaulNutzung(2100)} haben', + '5.3.1.2', self.__class__.__name__) + + if self.GFZ and (self.GFZmax or self.GFZmin): + raise ConformityException('Die Attribute GFZmin, GFZmax und GFZ' + 'dürfen nur in folgenden Kombinationen belegt werden: ' + '
    • GFZ
    • GFZmin und GFZmax
    ', '5.3.1.4', + self.__class__.__name__) + if (self.GFZmin and (self.GFZ or not self.GFZmax)) or (self.GFZmax and (self.GFZ or not self.GFZmin)): + raise ConformityException('Die Attribute GFZmin, GFZmax und GFZ' + 'dürfen nur in folgenden Kombinationen belegt werden: ' + '
    • GFZ
    • GFZmin und GFZmax
    ', '5.3.1.4', + self.__class__.__name__) diff --git a/src/SAGisXPlanung/FPlan/FP_Gemeinbedarf/__init__.py b/src/SAGisXPlanung/FPlan/FP_Gemeinbedarf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/FPlan/FP_Gemeinbedarf/data_types.py b/src/SAGisXPlanung/FPlan/FP_Gemeinbedarf/data_types.py new file mode 100644 index 0000000..9725ef7 --- /dev/null +++ b/src/SAGisXPlanung/FPlan/FP_Gemeinbedarf/data_types.py @@ -0,0 +1,51 @@ +from uuid import uuid4 + +from sqlalchemy import Column, Enum, String, ForeignKey +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from SAGisXPlanung import Base +from SAGisXPlanung.XPlan.enums import XP_ZweckbestimmungGemeinbedarf, XP_ZweckbestimmungSpielSportanlage +from SAGisXPlanung.XPlan.mixins import RelationshipMixin, ElementOrderMixin + + +class FP_KomplexeZweckbestGemeinbedarf(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung einer Fläche für Gemeinbedarf """ + + __tablename__ = 'fp_zweckbestimmung_gemeinbedarf' + __avoidRelation__ = ['gemeinbedarf'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_ZweckbestimmungGemeinbedarf), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + gemeinbedarf_id = Column(UUID(as_uuid=True), ForeignKey('fp_gemeinbedarf.id', ondelete='CASCADE')) + gemeinbedarf = relationship('FP_Gemeinbedarf', back_populates='rel_zweckbestimmung') + + @classmethod + def avoid_export(cls): + return ['gemeinbedarf_id'] + + +class FP_KomplexeZweckbestSpielSportanlage(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung einer Spiel- und Sportanlage.""" + + __tablename__ = 'fp_zweckbestimmung_sport' + __avoidRelation__ = ['sportanlage'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_ZweckbestimmungSpielSportanlage), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + sportanlage_id = Column(UUID(as_uuid=True), ForeignKey('fp_spiel_sportanlage.id', ondelete='CASCADE')) + sportanlage = relationship('FP_SpielSportanlage', back_populates='rel_zweckbestimmung') + + @classmethod + def avoid_export(cls): + return ['sportanlage_id'] diff --git a/src/SAGisXPlanung/FPlan/FP_Gemeinbedarf/feature_types.py b/src/SAGisXPlanung/FPlan/FP_Gemeinbedarf/feature_types.py new file mode 100644 index 0000000..8d542e3 --- /dev/null +++ b/src/SAGisXPlanung/FPlan/FP_Gemeinbedarf/feature_types.py @@ -0,0 +1,163 @@ +from typing import List + +from qgis.core import (QgsSimpleFillSymbolLayer, QgsSymbol, QgsWkbTypes, QgsSingleSymbolRenderer, QgsSymbolLayerUtils, + QgsSimpleLineSymbolLayer, QgsMarkerLineSymbolLayer, QgsCentroidFillSymbolLayer, + QgsSvgMarkerSymbolLayer, QgsMarkerSymbol, QgsUnitTypes, QgsMapUnitScale, QgsRuleBasedRenderer) +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import QSize, Qt + +from sqlalchemy import Column, ForeignKey, Enum, ARRAY, String +from sqlalchemy.orm import declared_attr, relationship + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.FPlan.FP_Basisobjekte.feature_types import FP_Objekt +from SAGisXPlanung.XPlan.core import XPCol, XPRelationshipProperty +from SAGisXPlanung.XPlan.enums import XP_ZweckbestimmungGemeinbedarf, XP_ZweckbestimmungSpielSportanlage, \ + XP_Traegerschaft +from SAGisXPlanung.XPlan.mixins import PolygonGeometry, MixedGeometry +from SAGisXPlanung.XPlan.types import GeometryType, XPEnum + + +class FP_Gemeinbedarf(MixedGeometry, FP_Objekt): + """ Darstellung von Flächen für den Gemeinbedarf nach § 5, Abs. 2, Nr. 2 BauGB """ + + __tablename__ = 'fp_gemeinbedarf' + __mapper_args__ = { + 'polymorphic_identity': 'fp_gemeinbedarf', + } + + id = Column(ForeignKey("fp_objekt.id", ondelete='CASCADE'), primary_key=True) + + zweckbestimmung = Column(ARRAY(Enum(XP_ZweckbestimmungGemeinbedarf))) + + @declared_attr + def zweckbestimmung(cls): + return XPCol(ARRAY(Enum(XP_ZweckbestimmungGemeinbedarf)), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_zweckbestimmung_attr) + + rel_zweckbestimmung = relationship("FP_KomplexeZweckbestGemeinbedarf", back_populates="gemeinbedarf", + cascade="all, delete", passive_deletes=True) + + traeger = Column(XPEnum(XP_Traegerschaft, include_default=True)) + zugunstenVon = Column(String) + + @classmethod + def import_zweckbestimmung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'zweckbestimmung' + else: + return 'rel_zweckbestimmung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_zweckbestimmung', xplan_attribute='zweckbestimmung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def polygon_symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#febae1')) + symbol.appendSymbolLayer(fill) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.5) + line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + line.setPenStyle(Qt.SolidLine) + symbol.appendSymbolLayer(line) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + if geom_type == QgsWkbTypes.PolygonGeometry: + return QgsSingleSymbolRenderer(cls.polygon_symbol()) + elif geom_type is not None: + return QgsSingleSymbolRenderer(QgsSymbol.defaultSymbol(geom_type)) + raise Exception('parameter geometryType should not be None') + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.polygon_symbol(), QSize(48, 48)) + + +class FP_SpielSportanlage(MixedGeometry, FP_Objekt): + """ Darstellung von Flächen für Spiel- und Sportanlagen nach §5, Abs. 2, Nr. 2 BauGB """ + + __tablename__ = 'fp_spiel_sportanlage' + __mapper_args__ = { + 'polymorphic_identity': 'fp_spiel_sportanlage', + } + + id = Column(ForeignKey("fp_objekt.id", ondelete='CASCADE'), primary_key=True) + + zweckbestimmung = Column(Enum(XP_ZweckbestimmungSpielSportanlage)) + + @declared_attr + def zweckbestimmung(cls): + return XPCol(Enum(XP_ZweckbestimmungSpielSportanlage), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_zweckbestimmung_attr) + + rel_zweckbestimmung = relationship("FP_KomplexeZweckbestSpielSportanlage", back_populates="sportanlage", + cascade="all, delete", passive_deletes=True) + + zugunstenVon = Column(String) + + @classmethod + def import_zweckbestimmung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'zweckbestimmung' + else: + return 'rel_zweckbestimmung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_zweckbestimmung', xplan_attribute='zweckbestimmung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def polygon_symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + + fill = QgsSimpleFillSymbolLayer(QColor('#ffffff')) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.5) + line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + line.setPenStyle(Qt.SolidLine) + + dotted_line_inner = QgsMarkerLineSymbolLayer() + dotted_line_inner.setColor(QColor(0, 0, 0)) + dotted_line_inner.setWidth(0.8) + dotted_line_inner.setOffset(2) + dotted_line_inner.setOutputUnit(QgsUnitTypes.RenderMapUnits) + + dotted_line_outer = dotted_line_inner.clone() + dotted_line_outer.setOffset(1) + dotted_line_outer.setOutputUnit(QgsUnitTypes.RenderMapUnits) + + symbol.appendSymbolLayer(fill) + symbol.appendSymbolLayer(line) + symbol.appendSymbolLayer(dotted_line_inner) + symbol.appendSymbolLayer(dotted_line_outer) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + if geom_type == QgsWkbTypes.PolygonGeometry: + return QgsSingleSymbolRenderer(cls.polygon_symbol()) + elif geom_type is not None: + return QgsSingleSymbolRenderer(QgsSymbol.defaultSymbol(geom_type)) + raise Exception('parameter geometryType should not be None') + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.polygon_symbol(), QSize(48, 48)) diff --git a/src/SAGisXPlanung/FPlan/FP_Landwirtschaft_Wald_und_Gruen/__init__.py b/src/SAGisXPlanung/FPlan/FP_Landwirtschaft_Wald_und_Gruen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/FPlan/FP_Landwirtschaft_Wald_und_Gruen/data_types.py b/src/SAGisXPlanung/FPlan/FP_Landwirtschaft_Wald_und_Gruen/data_types.py new file mode 100644 index 0000000..daba703 --- /dev/null +++ b/src/SAGisXPlanung/FPlan/FP_Landwirtschaft_Wald_und_Gruen/data_types.py @@ -0,0 +1,72 @@ +from uuid import uuid4 + +from sqlalchemy import Column, Enum, String, ForeignKey +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from SAGisXPlanung import Base +from SAGisXPlanung.XPlan.enums import XP_ZweckbestimmungGruen, XP_ZweckbestimmungLandwirtschaft, XP_ZweckbestimmungWald +from SAGisXPlanung.XPlan.mixins import RelationshipMixin, ElementOrderMixin + + +class FP_KomplexeZweckbestGruen(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung einer Grünfläche """ + + __tablename__ = 'fp_zweckbestimmung_gruen' + __avoidRelation__ = ['gruenflaeche'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_ZweckbestimmungGruen), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + gruenflaeche_id = Column(UUID(as_uuid=True), ForeignKey('fp_gruen.id', ondelete='CASCADE')) + gruenflaeche = relationship('FP_Gruen', back_populates='rel_zweckbestimmung') + + @classmethod + def avoid_export(cls): + return ['gruenflaeche_id'] + + +class FP_KomplexeZweckbestLandwirtschaft(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung einer Fläche für die Landwirtschaft """ + + __tablename__ = 'fp_zweckbestimmung_landwirtschaft' + __avoidRelation__ = ['landwirtschaft'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_ZweckbestimmungLandwirtschaft), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + landwirtschaft_id = Column(UUID(as_uuid=True), ForeignKey('fp_landwirtschaft.id', ondelete='CASCADE')) + landwirtschaft = relationship('FP_Landwirtschaft', back_populates='rel_zweckbestimmung') + + @classmethod + def avoid_export(cls): + return ['landwirtschaft_id'] + + +class FP_KomplexeZweckbestWald(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung einer Waldfläche """ + + __tablename__ = 'fp_zweckbestimmung_wald' + __avoidRelation__ = ['waldflaeche'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(XP_ZweckbestimmungWald), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + waldflaeche_id = Column(UUID(as_uuid=True), ForeignKey('fp_wald.id', ondelete='CASCADE')) + waldflaeche = relationship('FP_WaldFlaeche', back_populates='rel_zweckbestimmung') + + @classmethod + def avoid_export(cls): + return ['waldflaeche_id'] diff --git a/src/SAGisXPlanung/FPlan/FP_Landwirtschaft_Wald_und_Gruen/feature_types.py b/src/SAGisXPlanung/FPlan/FP_Landwirtschaft_Wald_und_Gruen/feature_types.py new file mode 100644 index 0000000..7610e34 --- /dev/null +++ b/src/SAGisXPlanung/FPlan/FP_Landwirtschaft_Wald_und_Gruen/feature_types.py @@ -0,0 +1,209 @@ +from typing import List + +from qgis.core import (QgsSimpleFillSymbolLayer, QgsSymbol, QgsWkbTypes, QgsSingleSymbolRenderer, QgsSymbolLayerUtils, + QgsSimpleLineSymbolLayer, QgsMarkerLineSymbolLayer, QgsCentroidFillSymbolLayer, + QgsSvgMarkerSymbolLayer, QgsMarkerSymbol, QgsUnitTypes, QgsMapUnitScale, QgsRuleBasedRenderer) +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import QSize, Qt + +from sqlalchemy import Column, ForeignKey, Enum, ARRAY +from sqlalchemy.orm import declared_attr, relationship + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.FPlan.FP_Basisobjekte.feature_types import FP_Objekt +from SAGisXPlanung.XPlan.core import XPCol, XPRelationshipProperty +from SAGisXPlanung.XPlan.enums import XP_ZweckbestimmungGruen, XP_Nutzungsform, XP_ZweckbestimmungLandwirtschaft, \ + XP_ZweckbestimmungWald, XP_EigentumsartWald, XP_WaldbetretungTyp +from SAGisXPlanung.XPlan.mixins import PolygonGeometry, FlaechenschlussObjekt, MixedGeometry +from SAGisXPlanung.XPlan.types import GeometryType, XPEnum + + +class FP_Gruen(MixedGeometry, FP_Objekt): + """ Darstellung einer Grünfläche nach § 5, Abs. 2, Nr. 5 BauGB """ + + __tablename__ = 'fp_gruen' + __mapper_args__ = { + 'polymorphic_identity': 'fp_gruen', + } + + id = Column(ForeignKey("fp_objekt.id", ondelete='CASCADE'), primary_key=True) + + @declared_attr + def zweckbestimmung(cls): + return XPCol(Enum(XP_ZweckbestimmungGruen), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_zweckbestimmung_attr) + + rel_zweckbestimmung = relationship("FP_KomplexeZweckbestGruen", back_populates="gruenflaeche", + cascade="all, delete", passive_deletes=True) + + nutzungsform = Column(XPEnum(XP_Nutzungsform, include_default=True)) + + @classmethod + def import_zweckbestimmung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'zweckbestimmung' + else: + return 'rel_zweckbestimmung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_zweckbestimmung', xplan_attribute='zweckbestimmung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def polygon_symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#7ec400')) + symbol.appendSymbolLayer(fill) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.5) + line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + line.setPenStyle(Qt.SolidLine) + symbol.appendSymbolLayer(line) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + if geom_type == QgsWkbTypes.PolygonGeometry: + return QgsSingleSymbolRenderer(cls.polygon_symbol()) + elif geom_type is not None: + return QgsSingleSymbolRenderer(QgsSymbol.defaultSymbol(geom_type)) + raise Exception('parameter geometryType should not be None') + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.polygon_symbol(), QSize(48, 48)) + + +class FP_Landwirtschaft(MixedGeometry, FP_Objekt): + """ Darstellung von Flächen für Spiel- und Sportanlagen nach §5, Abs. 2, Nr. 2 BauGB """ + + __tablename__ = 'fp_landwirtschaft' + __mapper_args__ = { + 'polymorphic_identity': 'fp_landwirtschaft', + } + + id = Column(ForeignKey("fp_objekt.id", ondelete='CASCADE'), primary_key=True) + + @declared_attr + def zweckbestimmung(cls): + return XPCol(Enum(XP_ZweckbestimmungLandwirtschaft), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_zweckbestimmung_attr) + + rel_zweckbestimmung = relationship("FP_KomplexeZweckbestLandwirtschaft", back_populates="landwirtschaft", + cascade="all, delete", passive_deletes=True) + + @classmethod + def import_zweckbestimmung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'zweckbestimmung' + else: + return 'rel_zweckbestimmung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_zweckbestimmung', xplan_attribute='zweckbestimmung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def polygon_symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#fff394')) + symbol.appendSymbolLayer(fill) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.5) + line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + line.setPenStyle(Qt.SolidLine) + symbol.appendSymbolLayer(line) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + if geom_type == QgsWkbTypes.PolygonGeometry: + return QgsSingleSymbolRenderer(cls.polygon_symbol()) + elif geom_type is not None: + return QgsSingleSymbolRenderer(QgsSymbol.defaultSymbol(geom_type)) + raise Exception('parameter geometryType should not be None') + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.polygon_symbol(), QSize(48, 48)) + + +class FP_WaldFlaeche(MixedGeometry, FlaechenschlussObjekt, FP_Objekt): + """ Darstellung von Waldflächen nach §5, Abs. 2, Nr. 9b, """ + + __tablename__ = 'fp_wald' + __mapper_args__ = { + 'polymorphic_identity': 'fp_wald', + } + + id = Column(ForeignKey("fp_objekt.id", ondelete='CASCADE'), primary_key=True) + + @declared_attr + def zweckbestimmung(cls): + return XPCol(Enum(XP_ZweckbestimmungWald), version=XPlanVersion.FIVE_THREE, + import_attr=cls.import_zweckbestimmung_attr) + + rel_zweckbestimmung = relationship("FP_KomplexeZweckbestWald", back_populates="waldflaeche", + cascade="all, delete", passive_deletes=True) + + eigentumsart = Column(XPEnum(XP_EigentumsartWald, include_default=True)) + betreten = Column(ARRAY(Enum(XP_WaldbetretungTyp))) + + @classmethod + def import_zweckbestimmung_attr(cls, version): + if version == XPlanVersion.FIVE_THREE: + return 'zweckbestimmung' + else: + return 'rel_zweckbestimmung' + + @classmethod + def xp_relationship_properties(cls) -> List[XPRelationshipProperty]: + return [ + XPRelationshipProperty(rel_name='rel_zweckbestimmung', xplan_attribute='zweckbestimmung', + allowed_version=XPlanVersion.SIX) + ] + + @classmethod + def polygon_symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#00cb4d')) + symbol.appendSymbolLayer(fill) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.5) + line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + line.setPenStyle(Qt.SolidLine) + symbol.appendSymbolLayer(line) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + if geom_type == QgsWkbTypes.PolygonGeometry: + return QgsSingleSymbolRenderer(cls.polygon_symbol()) + elif geom_type is not None: + return QgsSingleSymbolRenderer(QgsSymbol.defaultSymbol(geom_type)) + raise Exception('parameter geometryType should not be None') + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.polygon_symbol(), QSize(48, 48)) diff --git a/src/SAGisXPlanung/FPlan/FP_Verkehr/__init__.py b/src/SAGisXPlanung/FPlan/FP_Verkehr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/FPlan/FP_Verkehr/enums.py b/src/SAGisXPlanung/FPlan/FP_Verkehr/enums.py new file mode 100644 index 0000000..d108b4a --- /dev/null +++ b/src/SAGisXPlanung/FPlan/FP_Verkehr/enums.py @@ -0,0 +1,37 @@ +from enum import Enum + +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin + + +class FP_ZweckbestimmungStrassenverkehr(XPlanungEnumMixin, Enum): + """ Zweckbestimmung des Straßen-Objektes """ + + Autobahn = 1000 + Hauptverkehrsstrasse = 1200 + Ortsdurchfahrt = 1300 + SonstigerVerkehrswegAnlage = 1400 + VerkehrsberuhigterBereich = 14000 + Platz = 14001 + Fussgaengerbereich = 14002 + RadGehweg = 14003 + Radweg = 14004 + Gehweg = 14005 + Wanderweg = 14006 + ReitKutschweg = 14007 + Rastanlage = 14008 + Busbahnhof = 14009 + UeberfuehrenderVerkehrsweg = 140010 + UnterfuehrenderVerkehrsweg = 140011 + Wirtschaftsweg = 140012 + LandwirtschaftlicherVerkehr = 140013 + RuhenderVerkehr = 1600 + Parkplatz = 16000 + FahrradAbstellplatz = 16001 + P_RAnlage = 16002 + CarSharing = 3000 + BikeSharing = 3100 + B_RAnlage = 3200 + Parkhaus = 3300 + Mischverkehrsflaeche = 3400 + Ladestation = 3500 + Sonstiges = 9999 diff --git a/src/SAGisXPlanung/FPlan/FP_Verkehr/feature_types.py b/src/SAGisXPlanung/FPlan/FP_Verkehr/feature_types.py new file mode 100644 index 0000000..ebac0a3 --- /dev/null +++ b/src/SAGisXPlanung/FPlan/FP_Verkehr/feature_types.py @@ -0,0 +1,55 @@ +from qgis.core import (QgsSimpleFillSymbolLayer, QgsSymbol, QgsWkbTypes, QgsSingleSymbolRenderer, QgsSymbolLayerUtils, + QgsSimpleLineSymbolLayer, QgsMarkerLineSymbolLayer, QgsCentroidFillSymbolLayer, + QgsSvgMarkerSymbolLayer, QgsMarkerSymbol, QgsUnitTypes, QgsMapUnitScale, QgsRuleBasedRenderer) +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import QSize, Qt + +from sqlalchemy import Column, ForeignKey, Enum + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.FPlan.FP_Basisobjekte.feature_types import FP_Objekt +from SAGisXPlanung.FPlan.FP_Verkehr.enums import FP_ZweckbestimmungStrassenverkehr +from SAGisXPlanung.XPlan.core import xp_version +from SAGisXPlanung.XPlan.enums import XP_Nutzungsform +from SAGisXPlanung.XPlan.mixins import PolygonGeometry +from SAGisXPlanung.XPlan.types import GeometryType, XPEnum + + +@xp_version(versions=[XPlanVersion.FIVE_THREE]) +class FP_Strassenverkehr(PolygonGeometry, FP_Objekt): + """ Darstellung einer Grünfläche nach § 5, Abs. 2, Nr. 5 BauGB """ + + __tablename__ = 'fp_strassenverkehr' + __mapper_args__ = { + 'polymorphic_identity': 'fp_strassenverkehr', + } + + id = Column(ForeignKey("fp_objekt.id", ondelete='CASCADE'), primary_key=True) + + zweckbestimmung = Column(Enum(FP_ZweckbestimmungStrassenverkehr)) + nutzungsform = Column(XPEnum(XP_Nutzungsform, include_default=True)) + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#ffb300')) + symbol.appendSymbolLayer(fill) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.5) + line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + line.setPenStyle(Qt.SolidLine) + symbol.appendSymbolLayer(line) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(48, 48)) diff --git a/src/SAGisXPlanung/FPlan/FP_Wasser/__init__.py b/src/SAGisXPlanung/FPlan/FP_Wasser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/FPlan/FP_Wasser/feature_types.py b/src/SAGisXPlanung/FPlan/FP_Wasser/feature_types.py new file mode 100644 index 0000000..735246a --- /dev/null +++ b/src/SAGisXPlanung/FPlan/FP_Wasser/feature_types.py @@ -0,0 +1,53 @@ +from qgis.core import (QgsSimpleFillSymbolLayer, QgsSymbol, QgsWkbTypes, QgsSingleSymbolRenderer, QgsSymbolLayerUtils, + QgsSimpleLineSymbolLayer, QgsMarkerLineSymbolLayer, QgsCentroidFillSymbolLayer, + QgsSvgMarkerSymbolLayer, QgsMarkerSymbol, QgsUnitTypes, QgsMapUnitScale, QgsRuleBasedRenderer) +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import QSize, Qt + +from sqlalchemy import Column, ForeignKey, Enum, ARRAY + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.FPlan.FP_Basisobjekte.feature_types import FP_Objekt +from SAGisXPlanung.XPlan.core import xp_version +from SAGisXPlanung.XPlan.enums import XP_ZweckbestimmungGewaesser +from SAGisXPlanung.XPlan.mixins import PolygonGeometry +from SAGisXPlanung.XPlan.types import GeometryType + + +@xp_version(versions=[XPlanVersion.FIVE_THREE]) +class FP_Gewaesser(PolygonGeometry, FP_Objekt): + """ Darstellung einer Grünfläche nach § 5, Abs. 2, Nr. 5 BauGB """ + + __tablename__ = 'fp_gewaesser' + __mapper_args__ = { + 'polymorphic_identity': 'fp_gewaesser', + } + + id = Column(ForeignKey("fp_objekt.id", ondelete='CASCADE'), primary_key=True) + + zweckbestimmung = Column(Enum(XP_ZweckbestimmungGewaesser)) + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#71f8ff')) + symbol.appendSymbolLayer(fill) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.5) + line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + line.setPenStyle(Qt.SolidLine) + symbol.appendSymbolLayer(line) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(48, 48)) diff --git a/src/SAGisXPlanung/FPlan/__init__.py b/src/SAGisXPlanung/FPlan/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/GML/GMLReader.py b/src/SAGisXPlanung/GML/GMLReader.py new file mode 100644 index 0000000..0618cbb --- /dev/null +++ b/src/SAGisXPlanung/GML/GMLReader.py @@ -0,0 +1,351 @@ +import datetime +import logging +import inspect + +from geoalchemy2 import Geometry, WKTElement +from lxml import etree +from osgeo import ogr +from sqlalchemy import inspect as s_insp, ARRAY +from sqlalchemy.orm.attributes import flag_modified + +from SAGisXPlanung import Base, XPlanVersion +from SAGisXPlanung.BuildingTemplateItem import BuildingTemplateCellDataType +from SAGisXPlanung.XPlan.XP_Praesentationsobjekte.feature_types import XP_Nutzungsschablone +from SAGisXPlanung.XPlan.types import RefURL +from SAGisXPlanung.utils import CLASSES, query_existing, PRE_FILLED_CLASSES + +logger = logging.getLogger(__name__) + + +class GMLReader: + """ + Ein GMLReader-Objekt konvertiert ein ein valides XPlanGML-Dokument der Version 5.3 in ein XP_Plan-Objekt + Der XP_Plan-Objekt kann über das Attribut 'plan' abgerufen werden. + """ + + def __init__(self, gml, files=None, progress_callback=None): + + from timeit import default_timer as timer + + start = timer() + + self.exeptions = [] + self.files = files if files else {} + + parser = etree.XMLParser(remove_blank_text=True) + self.root = etree.fromstring(gml, parser=parser) + + self.nsmap = self.root.nsmap + # remove the None entry (top level namespace) if it exists - xpath does not allow it in the namespace map + self.nsmap.pop(None, None) + self.import_version = XPlanVersion.from_namespace(self.nsmap['xplan']) + + self.object_count = int(self.root.xpath("count(//gml:featureMember)", namespaces=self.nsmap)) + self.progress_callback = progress_callback + self.current_progress = 0 + self.setProgress(0) + + plan_element = self.root.xpath(".//xplan:*[contains(name(),'_Plan')][1]", namespaces=self.nsmap)[0] + type_name = etree.QName(plan_element).localname + self.type = CLASSES[type_name] + self.plan = self.readPlan(plan_element) + + # ... + end = timer() + logger.debug(f'elapsed time {end - start}') + + def setProgress(self, progress): + if not self.progress_callback: + return + self.progress_callback((self.current_progress, self.object_count)) + self.current_progress = progress + + def readPlan(self, gml): + """ + Liest den Basisknoten eines Planwerks aus. + + Parameters + ---------- + gml: lxml.etree.Element + XPlanGML-Knoten eines Planwerk-Objekts (xplan:*_Plan) + Returns + ------- + any: + Objekt vom Typ des XPlanGML-Knoten + + """ + self.plan = self.type() + gml_id = gml.xpath('@gml:id', namespaces=self.nsmap)[0] + self.plan.id = gml_id[gml_id.find('_')+1:] + for node in gml.iterchildren(): + node_name = etree.QName(node).localname + + if node_name == "boundedBy": + continue + elif node_name == "bereich": + bereich_id = node.xpath('@xlink:href', namespaces=self.nsmap)[0] + bereich_id = str(bereich_id).replace('#', '') + bereich = self.root.xpath(f"//*[@gml:id='{bereich_id}'][1]", namespaces=self.nsmap)[0] + value = self.readXPBereich(bereich) + getattr(self.plan, node_name).append(value) + elif node_name in [r[0] for r in self.plan.relationships()]: + if len(node) == 0: + # node is an xlink reference not a subobject, this should not be hit + raise NotImplementedError('reading xlink references on XP_Plan not implemented') + else: + value = self.read_data_object(node[0], files=self.files) + if value.__class__ in PRE_FILLED_CLASSES: + obj_from_db = query_existing(value) + value = obj_from_db if obj_from_db is not None else value + if (a := getattr(self.plan, node_name)) is not None: + a.append(value) + else: + setattr(self.plan, node_name, value) + elif hasattr(self.plan, node_name): + col_type = getattr(self.type, node_name).property.columns[0].type + GMLReader.read_attribute(col_type, node_name, self.plan, node) + + self.setProgress(self.current_progress + 1) + return self.plan + + + @staticmethod + def readGeometry(gml_node) -> WKTElement: + """ + Liest eine beliebige Geometrie aus einem GML-Knoten aus. + ---------- + gml: lxml.etree.Element + GML-Knoten vom Typ gml:GeometryPropertyType + + Returns + ------- + geoalchemy2.elements.WKTElement + WKT der eingelesenen Geometrie + """ + try: + srs_name = gml_node.attrib['srsName'] + except KeyError as e: + srs_name_list = gml_node.xpath('//*[@srsName]/@srsName[1]', namespaces=gml_node.nsmap) + if not srs_name_list: + raise Exception([e, Exception(f"Attribut 'srsName' in Zeile {gml_node.sourceline} erwartet")]) + srs_name = srs_name_list[0] + + srid = int(srs_name.partition(':')[-1]) + + gml_string = etree.tostring(gml_node).decode() + geom = ogr.CreateGeometryFromGML(gml_string) + + return WKTElement(geom.ExportToWkt(), srid=srid) + + def readXPBereich(self, gml): + """ + Liest den GML-Knoten eines Planbereichs aus. + + Parameters + ---------- + gml: lxml.etree.Element + XPlanGML-Knoten eines Planbereich-Objekts (xplan:*_Bereich) + Returns + ------- + any: + Objekt vom Typ des XPlanGML-Knoten + + """ + type_name = etree.QName(gml).localname + bereich_type = CLASSES[type_name] + bereich = bereich_type() + + gml_id = gml.xpath('@gml:id', namespaces=self.nsmap)[0] + bereich.id = gml_id[gml_id.find('_') + 1:] + + for node in gml.iterchildren(): + node_name = etree.QName(node).localname + if node_name == "boundedBy" or node_name == "gehoertZuPlan": + continue + + if node_name == "refScan": + value = self.read_data_object(node[0], files=self.files) + getattr(bereich, node_name).append(value) + continue + elif node_name == 'planinhalt': + plancontent_id = node.xpath('@xlink:href', namespaces=self.nsmap)[0] + plancontent_id = str(plancontent_id).lstrip('#') + plancontent = self.root.xpath(f"//*[@gml:id='{plancontent_id}'][1]", namespaces=self.nsmap)[0] + value = self.readXPObjekt(plancontent) + if value: + getattr(bereich, node_name).append(value) + + gml.remove(node) + del node + continue + + if hasattr(bereich, node_name) and node_name not in [r[0] for r in bereich.relationships()]: + col_type = getattr(bereich_type, node_name).property.columns[0].type + GMLReader.read_attribute(col_type, node_name, bereich, node) + + self.setProgress(self.current_progress + 1) + return bereich + + def readXPObjekt(self, gml): + """ + Erstellt aus einem XPlanGML-Knoten eine XP_Objekt-Instanz. + + Parameters + ---------- + gml: lxml.etree.Element + XPlanGML-Knoten einer von XP_Objekt abgeleiteten Klasse (alle vektoriellen Planinhalte) + """ + type_name = etree.QName(gml).localname + if type_name not in CLASSES.keys(): + return + object_type = CLASSES[type_name] + obj = object_type() + + obj_id = gml.xpath('@gml:id', namespaces=self.nsmap)[0] + obj.id = obj_id[obj_id.find('_') + 1:] + + for node in gml.iterchildren(): + node_name = etree.QName(node).localname + if not hasattr(obj, node_name): + continue + if node_name in ['gehoertZuBereich', 'dientZurDarstellungVon']: + continue + + col = getattr(object_type, node_name) + + if hasattr(col, 'import_attr') and col.import_attr is not None: + node_name = col.import_attr(self.import_version) + + if node_name in [r[0] for r in obj.relationships()]: + # find node content if relationship is not immediately child but instead linked via xlink + if len(node) == 0: + xlink_refs = node.xpath('@xlink:href', namespaces=self.nsmap) + if not xlink_refs: + continue + linked_node_id = str(xlink_refs[0]).lstrip('#') + linked_node = self.root.xpath(f"(//*[@gml:id='{linked_node_id}'])[1]", namespaces=self.nsmap) + if not linked_node: + self.exeptions.append(Exception(f'xlink verweist auf ein Objekt, das nicht in der XPlanGML-Datei' + f' vorliegt. (ID: {linked_node_id}, Zeile: {node.sourceline})')) + continue + + node.append(linked_node[0]) + value = self.readXPObjekt(linked_node[0]) + + if isinstance(value, XP_Nutzungsschablone): + value.hidden = False + if value.zeilenAnz is not None: + value.set_defaults(int(value.zeilenAnz)) + getattr(obj, node_name)[0] = value + continue + else: + value = self.read_data_object(node[0], files=self.files) + if value.__class__ in PRE_FILLED_CLASSES: + obj_from_db = query_existing(value) + if obj_from_db is not None: + # object could already be in session from previous loops, therefore store only id + node_name = f'{node_name}_id' + value = obj_from_db.id + if (a := getattr(obj, node_name)) is not None: + a.append(value) + else: + setattr(obj, node_name, value) + + gml.remove(node) + del node + continue + + # edge case where same named column exists in base class which should be used + if not obj.__class__.attr_fits_version(node_name, self.import_version): + base_classes = [c for c in list(inspect.getmro(obj.__class__)) if issubclass(c, Base)] + cls = next(c for c in reversed(base_classes) if hasattr(c, node_name)) + col_type = getattr(cls, node_name).property.columns[0].type + else: + col_type = getattr(obj.__class__, node_name).property.columns[0].type + + GMLReader.read_attribute(col_type, node_name, obj, node) + + self.setProgress(self.current_progress + 1) + return obj + + @staticmethod + def read_attribute(col_type, node_name, obj, node): + logger.debug(f'node {node_name} col {col_type}, obj class {obj.__class__} node {node}') + + if isinstance(col_type, Geometry): + logger.debug(node[0]) + value = GMLReader.readGeometry(node[0]) + if value is None: + return + setattr(obj, node_name, value) + return + + value = node.text + if hasattr(col_type, 'enums'): + try: + if col_type.enum_class is not None: + value = col_type.enum_class(int(value)) + setattr(obj, node_name, value) + except ValueError: + setattr(obj, node_name, col_type.enum_class(value)) + elif col_type.python_type == bool: + setattr(obj, node_name, bool(value)) + elif isinstance(col_type, ARRAY) and hasattr(col_type.item_type, 'enums'): + try: + value = col_type.item_type.enum_class(int(value)) + getattr(obj, node_name).append(value) + except Exception as e: + setattr(obj, node_name, [value]) + elif col_type.python_type == datetime.date: + setattr(obj, node_name, datetime.datetime.strptime(value, '%Y-%m-%d')) + elif col_type.python_type == list and col_type.item_type.python_type == datetime.date: + getattr(obj, node_name).append(datetime.datetime.strptime(value, '%Y-%m-%d')) + else: + setattr(obj, node_name, value) + + @staticmethod + def read_data_object(gml, files=None, only_attributes=False): + """ + Wandelt ein XPlanGML-Datatype in ein ORM-Objekt des gleichen Typs um. + + Parameters + ---------- + gml: lxml.etree.Element + XPlanGML-Knoten eines XPlanGML-Datatype + files: dict + Dictionary aus Dateiname und Datei + only_attributes: bool + Wenn falsch, kein Aufruf der klasssenspezifischen XPlan-Import Routinen (from_xplan_node) + Returns + ------- + any: + ORM-Objekt vom Typ des XPlanGML-Knoten + + """ + if files is None: + files = {} + + type_name = etree.QName(gml).localname + object_type = CLASSES[type_name] + + if not only_attributes and hasattr(object_type, 'from_xplan_node'): + return object_type.from_xplan_node(gml) + + obj = object_type() + for node in gml.iterchildren(): + node_name = etree.QName(node).localname + value = node.text + + if hasattr(obj, node_name): + try: + col_type = getattr(object_type, node_name).property.columns[0].type + except AttributeError as e: + # continue when property is not a column (but a relation instead) + # this fail check is a lot faster than doing a lookup whether attr is in relationship properties + continue + GMLReader.read_attribute(col_type, node_name, obj, node) + + if isinstance(col_type, RefURL) and hasattr(obj, 'file') and value in files: + setattr(obj, 'file', files[value]) + + return obj diff --git a/src/SAGisXPlanung/GML/GMLWriter.py b/src/SAGisXPlanung/GML/GMLWriter.py new file mode 100644 index 0000000..031b7d5 --- /dev/null +++ b/src/SAGisXPlanung/GML/GMLWriter.py @@ -0,0 +1,336 @@ +import logging +import itertools +from enum import Enum +from io import BytesIO +from pathlib import PurePath +from uuid import uuid4 +from zipfile import ZipFile + +from lxml import etree +from geoalchemy2 import WKBElement, WKTElement +from osgeo import ogr, osr + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.GML.geometry import enforce_wkb_constraints + +from SAGisXPlanung.XPlan.data_types import XP_ExterneReferenz +from SAGisXPlanung.XPlan.feature_types import XP_Plan, XP_Bereich +from SAGisXPlanung.utils import is_url + +logger = logging.getLogger(__name__) + + +class GMLWriter: + """ + Ein GMLWriter-Objekt konvertiert ein XP_Plan-Objekt in ein valides XPlanGML Dokument der Version 5.3 + Der XPlanGML-Knoten kann über das Attribut 'root' abgerufen werden. + """ + + nsmap = { + "xsi": "http://www.w3.org/2001/XMLSchema-instance", + "wfs": "http://www.opengis.net/wfs", + "xlink": "http://www.w3.org/1999/xlink", + "gml": "http://www.opengis.net/gml/3.2" + } + + version_urls = { + XPlanVersion.FIVE_THREE: "http://www.xplanung.de/xplangml/5/3", + XPlanVersion.SIX: "http://www.xplanung.de/xplangml/6/0" + } + + def __init__(self, plan: XP_Plan, version=XPlanVersion.FIVE_THREE, root_tag=None): + + self.files = {} + self.version = version + self.nsmap['xplan'] = self.version_urls[version] + + self.root = etree.Element(root_tag if root_tag else f"{{{self.nsmap['xplan']}}}XPlanAuszug", + {f"{{{self.nsmap['gml']}}}id": f"GML_{uuid4()}"}, nsmap=self.nsmap) + + self.plan_name = plan.name + + self.root.append(self.writeEnvelope(plan.raeumlicherGeltungsbereich)) + self.root.append(self.writePlan(plan)) + for b in plan.bereich: + self.root.append(self.writeXPBereich(b)) + + def toGML(self) -> bytes: + xml = etree.tostring(self.root, pretty_print=True, xml_declaration=True, encoding='UTF-8', standalone=True) + return xml + + def toArchive(self) -> BytesIO: + zip_buffer = BytesIO() + with ZipFile(zip_buffer, mode='w') as zip_file: + for elm in self.root.findall(".//xplan:referenzURL", namespaces=self.root.nsmap): + if not is_url(elm.text): + if elm.text not in self.files: + raise ValueError(f'Datei {elm.text} konnte nicht gefunden werden') + + from qgis.PyQt.QtCore import QSettings + qs = QSettings() + path_prefix = qs.value(f"plugins/xplanung/export_path", '') + file = self.files[elm.text] + elm.text = f'{path_prefix}{PurePath(elm.text).name}' + zip_file.writestr(elm.text, file) + + file_name = self.plan_name.replace("/", "-").replace('"', '\'') + zip_file.writestr(f"{file_name}.gml", self.toGML()) + + return zip_buffer + + def writeEnvelope(self, geom: WKBElement) -> etree.Element: + """ + Erstellt einen boundedBy GML-Knoten aus einer beliebigen Geometrie + + Parameters + ---------- + geom: geoalchemy2.elements.WKBElement + WKB einer beliebigen Geometrie + + Returns + ------- + lxml.etree.Element + boundedBy-Knoten der Polygon-Geometrie + + """ + srs = f'EPSG:{geom.srid}' + + if isinstance(geom, WKBElement): + wkb_hex = enforce_wkb_constraints(geom.data.hex()) + ogr_geom = ogr.CreateGeometryFromWkb(bytes.fromhex(wkb_hex)) + elif isinstance(geom, WKTElement): + ogr_geom = ogr.CreateGeometryFromWkt(geom.data) + else: + raise AttributeError('unexpected geometry type') + bounds = ogr_geom.GetEnvelope() + boundedBy = etree.Element(f"{{{self.nsmap['gml']}}}boundedBy") + envelope = etree.SubElement(boundedBy, f"{{{self.nsmap['gml']}}}Envelope", {"srsName": srs}) + etree.SubElement(envelope, f"{{{self.nsmap['gml']}}}lowerCorner").text = str(bounds[0]) + ' ' + str(bounds[2]) + etree.SubElement(envelope, f"{{{self.nsmap['gml']}}}upperCorner").text = str(bounds[1]) + ' ' + str(bounds[3]) + + return boundedBy + + def writePlan(self, plan): + """ + Erstellt einen XML-Knoten der entsprechenden Planart. + + Parameters + ---------- + plan: XP_Plan + XP_Plan Objekt + Returns + ------- + lxml.etree.Element + -Knoten des XP_Plan Objekts + + """ + feature = etree.Element(f"{{{self.nsmap['gml']}}}featureMember", nsmap=self.nsmap) + xplan = etree.SubElement(feature, f"{{{self.nsmap['xplan']}}}{plan.__class__.__name__}", + {f"{{{self.nsmap['gml']}}}id": f"GML_{plan.id}"}) + xplan.append(self.writeEnvelope(plan.raeumlicherGeltungsbereich)) + + elements = plan.__class__.element_order(version=self.version) + for attr in elements: + value = getattr(plan, attr) + if value is None: + continue + if isinstance(value, list) and not value: + continue + if attr == "gemeinde" or attr == "externeReferenz" or attr == "verfahrensMerkmale": + for g in value: + f = etree.SubElement(xplan, f"{{{self.nsmap['xplan']}}}{attr}") + f.append(self.writeSubObject(g)) + continue + if isinstance(value, Enum) and hasattr(value, 'version'): + if value.version not in [None, self.version]: + continue + f = etree.SubElement(xplan, f"{{{self.nsmap['xplan']}}}{attr}") + if attr == "raeumlicherGeltungsbereich": + f.append(self.writeGeometry(value)) + elif attr == "bereich": + f.attrib[f"{{{self.nsmap['xlink']}}}href"] = f"#GML_{value[0].id}" + for bereich in value[1:]: + etree.SubElement(xplan, f"{{{self.nsmap['xplan']}}}{attr}", + {f"{{{self.nsmap['xlink']}}}href": f"#GML_{bereich.id}"}) + elif attr == "plangeber" or attr == "rel_veraenderungssperre": + rel = getattr(plan.__class__, attr).property + attr, _ = plan.__class__.relation_prop_display((attr, rel)) + f.tag = f"{{{self.nsmap['xplan']}}}{attr.lower()}" + f.append(self.writeSubObject(value)) + elif isinstance(value, list) and value: # TODO: `and value` can never be reached?; also move to further up and make full loop + f.text = writeTextNode(value[0]) + for e in value[1:]: + if isinstance(e, Enum) and hasattr(e, 'version'): + if e.version not in [None, self.version]: + continue + el = etree.SubElement(xplan, f"{{{self.nsmap['xplan']}}}{attr}") + el.text = writeTextNode(e) + elif dict(plan.relationships()).get(attr, None): + f.append(self.writeSubObject(value)) + else: + f.text = writeTextNode(value) + + return feature + + def writeGeometry(self, geom): + """ + Erstellt einen GML-Knoten für eine beliebige Geometrie + Parameters + ---------- + geom: geoalchemy2.elements._SpatialElement + WKT oder WKB einer (Multi)Polygon-Geometrie + + Returns + ------- + lxml.etree.Element + GML-Knoten der Geometrie + """ + if isinstance(geom, WKBElement): + wkb_hex = enforce_wkb_constraints(geom.data.hex()) + ogr_geom = ogr.CreateGeometryFromWkb(bytes.fromhex(wkb_hex)) + elif isinstance(geom, WKTElement): + ogr_geom = ogr.CreateGeometryFromWkt(geom.data) + else: + raise AttributeError('unexpected geometry type') + + srs = osr.SpatialReference() + srs.ImportFromEPSG(geom.srid) + ogr_geom.AssignSpatialReference(srs) + + gml = ogr_geom.ExportToGML(options=["FORMAT=GML32", f"GMLID=GML_{uuid4()}", "GML3_LONGSRS=NO", "NAMESPACE_DECL=YES"]) + return parse_etree(gml) + + def writeSubObject(self, obj): + """ + Erstellt einen XPlanGML-Knoten aus einem simplen XPlan-Basisobjekt, das über keine weiteren Relationen verfügt + + Parameters + ---------- + obj: + XPlan-Basisobjekt + + Returns + ------- + lxml.etree.Element + Zum Objekt korrespondierender XPlanGML-Knoten + + Examples + -------- + >>> gemeinde = XP_Gemeinde() + >>> gemeinde.ags = "37815" + >>> gemeinde.gemeindeName = "Berlin" + >>> node = self.writeSubObject(gemeinde) + >>> etree.tostring(node) + + 37815 + Berlin + + + """ + o = etree.Element(f"{{{self.nsmap['xplan']}}}{obj.__class__.__name__}", nsmap=self.nsmap) + + if isinstance(obj, XP_ExterneReferenz): + file = getattr(obj, 'file') + if file is not None: + self.files[obj.referenzURL] = (getattr(obj, 'file')) + + self.write_attributes(o, obj, version=self.version) + + return o + + def writeXPBereich(self, bereich): + """ + Erstellt einen XPlanGML-Knoten aus einem XP_Bereich-Objekt. Für ein valides XPlanGML muss das Feld + gehoertZuPlan_id belegt sein, um die Referenz zu eine XP_Plan-Objekt herzustellen. + + Parameters + ---------- + bereich: XP_Bereich + XP_Bereich-Objekt eines Plans + + Returns + ------- + lxml.etree.Element + XPlanGML-Knoten des Bereichs + """ + feature = etree.Element(f"{{{self.nsmap['gml']}}}featureMember", nsmap=self.nsmap) + xp_bereich = etree.SubElement(feature, f"{{{self.nsmap['xplan']}}}{bereich.__class__.__name__}", + {f"{{{self.nsmap['gml']}}}id": f"GML_{bereich.id}"}) + if bereich.geltungsbereich: + xp_bereich.append(self.writeEnvelope(bereich.geltungsbereich)) + + for attr in bereich.__class__.element_order(version=self.version): + if attr in ['praesentationsobjekt', 'simple_geometry', 'planinhalt']: + continue + value = getattr(bereich, attr) + if attr == "gehoertZuPlan_id": + etree.SubElement(xp_bereich, f"{{{self.nsmap['xplan']}}}gehoertZuPlan", + {f"{{{self.nsmap['xlink']}}}href": f"#GML_{value}"}) + continue + if value is None or isinstance(value, XP_Plan): + continue + if attr == "refScan": + for r in bereich.refScan: + f = etree.SubElement(xp_bereich, f"{{{self.nsmap['xplan']}}}{attr}") + f.append(self.writeSubObject(r)) + continue + if isinstance(value, Enum) and hasattr(value, 'version'): + if value.version not in [None, self.version]: + continue + f = etree.SubElement(xp_bereich, f"{{{self.nsmap['xplan']}}}{attr}") + if attr == "geltungsbereich": + f.append(self.writeGeometry(value)) + else: + f.text = writeTextNode(value) + + return feature + + @staticmethod + def writeUOM(node, attr, obj): + """ Fügt einem XML-Knoten je nach Datentyp die passende XPlanGML-Einheit als Attribut hinzu""" + try: + node.attrib['uom'] = getattr(obj.__class__, attr).property.columns[0].type.UOM + except: + pass + + @staticmethod + def write_attributes(node, xplan_object, version: XPlanVersion): + for attr in xplan_object.__class__.element_order(version=version): + if attr not in vars(xplan_object).keys(): # TODO: is this check needed? + continue + value = getattr(xplan_object, attr) + if value is None: + continue + if isinstance(value, Enum) and hasattr(value, 'version'): + if value.version not in [None, version]: + continue + field = etree.SubElement(node, f"{{{node.nsmap['xplan']}}}{attr}") + field.text = writeTextNode(value) + GMLWriter.writeUOM(field, attr, xplan_object) + + +def writeTextNode(value): + """ + Wandelt einen beliebigen Wert in eine Textrepräsentation um. Nützlich um diese danach als Inhalt + eines XML-Knotens zu verwenden. + + Parameters + ---------- + value + + Returns + ------- + str: + Textinhalt des eingebenen Werts + + """ + if isinstance(value, Enum): + return str(value.value) + if type(value) is bool: + return str(value).lower() + return str(value) + + +def parse_etree(xml_string: str) -> etree.Element: + parser = etree.XMLParser(recover=True, remove_blank_text=True) + return etree.fromstring(xml_string, parser) \ No newline at end of file diff --git a/src/SAGisXPlanung/GML/__init__.py b/src/SAGisXPlanung/GML/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/GML/geometry.py b/src/SAGisXPlanung/GML/geometry.py new file mode 100644 index 0000000..0d5eada --- /dev/null +++ b/src/SAGisXPlanung/GML/geometry.py @@ -0,0 +1,41 @@ +from typing import Union + +from geoalchemy2 import WKBElement, WKTElement +from qgis.core import QgsGeometry, QgsWkbTypes + +from SAGisXPlanung.XPlan.types import GeometryType + +SRID_FLAG = 0x20000000 # flag byte denoting that a srid is embedded within EWKB + + +def enforce_wkb_constraints(ewkb: str) -> str: + """ Enforces correctness of WKB hex string by converting possible EWKB to WKB in order to create QgsGeometry""" + geomType_uint32 = int(int(ewkb[2:10], 16).to_bytes(4, byteorder='little').hex(), 16) + if geomType_uint32 & SRID_FLAG == SRID_FLAG: + geomType_uint32 = ~SRID_FLAG & geomType_uint32 + geomType_hex = geomType_uint32.to_bytes(4, byteorder='little').hex() + ewkb = ewkb[:2] + geomType_hex + ewkb[18:] + + return ewkb + + +def geometry_from_spatial_element(element: Union[WKBElement, WKTElement]) -> QgsGeometry: + """ Converts a geometry coming from a ORM object to a QgsGeometry object""" + if isinstance(element, WKTElement): + geom = QgsGeometry.fromWkt(element.data) + return geom + elif isinstance(element, WKBElement): + wkb_hex = enforce_wkb_constraints(element.data.hex()) + geom = QgsGeometry() + geom.fromWkb(bytes.fromhex(wkb_hex)) + return geom + raise Exception('Could not convert to geometry. No WKBElement/WKTElement given') + + +def geom_type_as_layer_url(geom_type: GeometryType) -> str: + """ Converts a GeometryType to a string that can be used in a layer url. + This is required because the displayString returns 'Line' which is not accepted by QgsVectorLayer provider""" + if geom_type == QgsWkbTypes.LineGeometry: + return 'linestring' + else: + return QgsWkbTypes.geometryDisplayString(geom_type) diff --git a/src/SAGisXPlanung/LPlan/LP_Basisobjekte/__init__.py b/src/SAGisXPlanung/LPlan/LP_Basisobjekte/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/LPlan/LP_Basisobjekte/enums.py b/src/SAGisXPlanung/LPlan/LP_Basisobjekte/enums.py new file mode 100644 index 0000000..4b8c58d --- /dev/null +++ b/src/SAGisXPlanung/LPlan/LP_Basisobjekte/enums.py @@ -0,0 +1,24 @@ +from enum import Enum + +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin + + +class LP_PlanArt(XPlanungEnumMixin, Enum): + """ Typ des vorliegenden Landschaftsplans. """ + + Landschaftsprogramm = 1000 + Landschaftsrahmenplan = 2000 + Landschaftsplan = 3000 + Gruenordnungsplan = 4000 + Sonstiges = 9999 + + +class LP_Rechtsstand(XPlanungEnumMixin, Enum): + """ Rechtsstand des Plans """ + + Aufstellungsbeschluss = 1000 + Entwurf = 2000 + Plan = 3000 + Wirksamkeit = 4000 + Untergegangen = 5000 + diff --git a/src/SAGisXPlanung/LPlan/LP_Basisobjekte/feature_types.py b/src/SAGisXPlanung/LPlan/LP_Basisobjekte/feature_types.py new file mode 100644 index 0000000..9d6dec5 --- /dev/null +++ b/src/SAGisXPlanung/LPlan/LP_Basisobjekte/feature_types.py @@ -0,0 +1,101 @@ +from typing import List + +from qgis.PyQt.QtCore import Qt +from qgis.PyQt.QtGui import QColor +from qgis.core import QgsSymbol, QgsWkbTypes, QgsSimpleLineSymbolLayer, QgsSingleSymbolRenderer + +from sqlalchemy import Column, ForeignKey, Enum, String, Date, ARRAY, Boolean +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.LPlan.LP_Basisobjekte.enums import LP_Rechtsstand, LP_PlanArt +from SAGisXPlanung.XPlan.core import XPCol, XPRelationshipProperty +from SAGisXPlanung.XPlan.data_types import XP_PlanXP_GemeindeAssoc +from SAGisXPlanung.XPlan.enums import XP_Bundeslaender +from SAGisXPlanung.XPlan.feature_types import XP_Plan, XP_Bereich +from SAGisXPlanung.XPlan.types import GeometryType + + +class LP_Plan(XP_Plan): + """ Die Klasse modelliert ein Planwerk mit landschaftsplanerischen Festlegungen, Darstellungen bzw. Festsetzungen. """ + + def __init__(self): + super().__init__() + self.auslegungsEndDatum = [] + self.auslegungsStartDatum = [] + self.traegerbeteiligungsStartDatum = [] + self.traegerbeteiligungsEndDatum = [] + + __tablename__ = 'lp_plan' + __mapper_args__ = { + 'polymorphic_identity': 'lp_plan', + } + + id = Column(ForeignKey("xp_plan.id", ondelete='CASCADE'), primary_key=True) + + bundesland = Column(Enum(XP_Bundeslaender), nullable=False) + rechtlicheAussenwirkung = Column(Boolean, nullable=False) + planArt = Column(ARRAY(Enum(LP_PlanArt)), nullable=False) + + planungstraegerGKZ = XPCol(String(), version=XPlanVersion.FIVE_THREE) + planungstraeger = XPCol(String(), version=XPlanVersion.FIVE_THREE) + + gemeinde = relationship("XP_Gemeinde", back_populates="lp_plans", secondary=XP_PlanXP_GemeindeAssoc, doc='Gemeinde') + + plangeber_id = XPCol(UUID(as_uuid=True), ForeignKey('xp_plangeber.id'), version=XPlanVersion.SIX) + plangeber = relationship("XP_Plangeber", back_populates="lp_plans", doc='Plangeber') + + rechtsstand = Column(Enum(LP_Rechtsstand)) + aufstellungsbeschlussDatum = Column(Date()) + + auslegungsDatum = XPCol(ARRAY(Date), version=XPlanVersion.FIVE_THREE) + tOeBbeteiligungsDatum = XPCol(ARRAY(Date), version=XPlanVersion.FIVE_THREE) + oeffentlichkeitsbeteiligungDatum = XPCol(ARRAY(Date), version=XPlanVersion.FIVE_THREE) + + auslegungsStartDatum = XPCol(ARRAY(Date), version=XPlanVersion.SIX) + auslegungsEndDatum = XPCol(ARRAY(Date), version=XPlanVersion.SIX) + tOeBbeteiligungsStartDatum = XPCol(ARRAY(Date), version=XPlanVersion.SIX) + tOeBbeteiligungsEndDatum = XPCol(ARRAY(Date), version=XPlanVersion.SIX) + oeffentlichkeitsBetStartDatum = XPCol(ARRAY(Date), version=XPlanVersion.SIX) + oeffentlichkeitsBetEndDatum = XPCol(ARRAY(Date), version=XPlanVersion.SIX) + + aenderungenBisDatum = Column(Date()) + entwurfsbeschlussDatum = Column(Date()) + planbeschlussDatum = Column(Date()) + inkrafttretenDatum = Column(Date()) + veroeffentlichungsDatum = XPCol(Date(), version=XPlanVersion.SIX) + sonstVerfahrensDatum = Column(Date()) + + sonstVerfahrensText = XPCol(String(), version=XPlanVersion.SIX) + startBedingungen = XPCol(String(), version=XPlanVersion.SIX) + endeBedingungen = XPCol(String(), version=XPlanVersion.SIX) + + bereich = relationship("LP_Bereich", back_populates="gehoertZuPlan", cascade="all, delete", doc='Bereich') + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.8) + line.setPenStyle(Qt.SolidLine) + + symbol.appendSymbolLayer(line) + return QgsSingleSymbolRenderer(symbol) + + +class LP_Bereich(XP_Bereich): + """ Ein Bereich eines Landschaftsplans. """ + + __tablename__ = 'lp_bereich' + __mapper_args__ = { + 'polymorphic_identity': 'lp_bereich', + } + + id = Column(ForeignKey("xp_bereich.id", ondelete='CASCADE'), primary_key=True) + + gehoertZuPlan_id = Column(UUID(as_uuid=True), ForeignKey('lp_plan.id', ondelete='CASCADE')) + gehoertZuPlan = relationship('LP_Plan', back_populates='bereich') diff --git a/src/SAGisXPlanung/LPlan/__init__.py b/src/SAGisXPlanung/LPlan/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/MapLayerRegistry.py b/src/SAGisXPlanung/MapLayerRegistry.py new file mode 100644 index 0000000..6b090c1 --- /dev/null +++ b/src/SAGisXPlanung/MapLayerRegistry.py @@ -0,0 +1,145 @@ +import logging +from collections import defaultdict +from typing import Union + +from qgis.PyQt import QtCore +from qgis.gui import QgsMapCanvasItem +from qgis.core import QgsVectorLayer, QgsProject, QgsMapLayer, QgsAnnotationLayer, QgsWkbTypes, Qgis +from qgis.utils import iface + +from SAGisXPlanung import Session +from SAGisXPlanung.XPlan.types import GeometryType +from SAGisXPlanung.XPlanungItem import XPlanungItem + +logger = logging.getLogger(__name__) + + +class Singleton(QtCore.QObject): + def __new__(cls, *args, **kwargs): + it = cls.__dict__.get("__it__") + if it is not None: + return it + cls.__it__ = it = QtCore.QObject.__new__(cls) + it.init(*args, **kwargs) + return it + + def init(self, *args, **kwargs): + pass + + +class MapLayerRegistry(Singleton): + _layers = [] + _canvasItems = defaultdict(list) + + def init(self): + QgsProject.instance().layerStore().layerWillBeRemoved.connect(self.removeLayer) + + def unload(self): + try: + QgsProject.instance().layerStore().layerWillBeRemoved.disconnect(self.removeLayer) + except Exception: + pass + + @property + def layers(self): + return self._layers + + def canvasItemsAtFeat(self, feat_xid: str): + return self._canvasItems.get(feat_xid, []) + + def addCanvasItem(self, item: QgsMapCanvasItem, feat_xid: str): + # if building template already exists replace with new one + self.removeCanvasItems(feat_xid) + self._canvasItems[feat_xid].append(item) + + def removeCanvasItems(self, feat_xid: str): + items = self._canvasItems.pop(feat_xid, []) + for item in items: + iface.mapCanvas().scene().removeItem(item) + item.updateCanvas() + del item + + def addLayer(self, layer: QgsMapLayer, group=None, add_to_legend=True): + if not (isinstance(layer, QgsVectorLayer) or isinstance(layer, QgsAnnotationLayer)): + return + + if layer in self._layers: + return + + self._layers.append(layer) + + if add_to_legend: + QgsProject.instance().addMapLayer(layer, False) + if group and isinstance(layer, QgsVectorLayer): + group.addLayer(layer) + elif group and isinstance(layer, QgsAnnotationLayer): + group.insertLayer(0, layer) + + if isinstance(layer, QgsVectorLayer): + layer.committedGeometriesChanges.connect(self.onGeometriesChanged) + + def removeLayer(self, layer_id): + layer = self.layerById(layer_id) + if not layer: + return + + # remove template items from canvas + if layer.customProperty('xplanung/type') == 'BP_BaugebietsTeilFlaeche': + for key in layer.customPropertyKeys(): + if 'xplanung/feat-' not in key: + continue + feat_id = layer.customProperty(key) + self.removeCanvasItems(feat_id) + + self._layers.remove(layer) + + def layerById(self, layer_id) -> Union[QgsVectorLayer, QgsAnnotationLayer]: + for lyr in self._layers: + if lyr.id() == layer_id: + return lyr + + def featureIsShown(self, feat_xid: str) -> bool: + for lyr in self._layers: + for key in lyr.customPropertyKeys(): + if 'xplanung/feat-' not in key: + continue + if lyr.customProperty(key) == feat_xid: + return True + return False + + def layerByFeature(self, feat_xid: str) -> Union[None, QgsVectorLayer, QgsAnnotationLayer]: + for lyr in self._layers: + for key in lyr.customPropertyKeys(): + if 'xplanung/feat-' not in key: + continue + if lyr.customProperty(key) == feat_xid: + return lyr + + def layerByXid(self, xplan_item: XPlanungItem, geom_type: GeometryType = None) -> Union[None, QgsVectorLayer, QgsAnnotationLayer]: + for lyr in self._layers: + xtype = lyr.customProperty('xplanung/type') + xid = lyr.customProperty('xplanung/plan-xid') + if xtype == xplan_item.xtype.__name__ and xid == xplan_item.plan_xid: + # make sure only layers of correct geometry type are returned (for vector layers only) + if geom_type is not None and isinstance(lyr, QgsVectorLayer) and geom_type != lyr.geometryType(): + continue + return lyr + + def onGeometriesChanged(self, layer_id, changed_geometries): + layer = self.layerById(layer_id) + for feat_id, geometry in changed_geometries.items(): + if not layer.customProperties().contains(f'xplanung/feat-{feat_id}'): + raise KeyError('Geometrieänderung einer XPlanung Fläche detektiert, ' + 'aber kein zugehöriges Objekt gefunden ') + xplanung_id = layer.customProperties().value(f'xplanung/feat-{feat_id}') + xplanung_type = layer.customProperties().value('xplanung/type') + + from SAGisXPlanung.utils import CLASSES + from SAGisXPlanung.XPlan.feature_types import XP_Objekt + + if issubclass(cls := CLASSES[xplanung_type], XP_Objekt): + return + + with Session.begin() as session: + plan_content = session.query(cls).get(xplanung_id) + plan_content.setGeometry(geometry) diff --git a/src/SAGisXPlanung/RPlan/RP_Basisobjekte/__init__.py b/src/SAGisXPlanung/RPlan/RP_Basisobjekte/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/RPlan/RP_Basisobjekte/enums.py b/src/SAGisXPlanung/RPlan/RP_Basisobjekte/enums.py new file mode 100644 index 0000000..e905da9 --- /dev/null +++ b/src/SAGisXPlanung/RPlan/RP_Basisobjekte/enums.py @@ -0,0 +1,58 @@ +from enum import Enum + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin + + +class RP_Art(XPlanungEnumMixin, Enum): + """ Art des Raumordnungsplans. """ + + Regionalplan = 1000 + SachlicherTeilplanRegionalebene = 2000 + SachlicherTeilplanLandesebene = 2001 + Braunkohlenplan = 3000 + LandesweiterRaumordnungsplan = 4000 + StandortkonzeptBund = 5000 + AWZPlan = 5001 + RaeumlicherTeilplan = 6000 + Sonstiges = 9999 + + +class RP_Verfahren(XPlanungEnumMixin, Enum): + """ Verfahrensstatus des Plans """ + + Aenderung = 1000 + Teilfortschreibung = 2000 + Neuaufstellung = 3000 + Gesamtfortschreibung = 4000 + Aktualisierung = 5000 + Neubekanntmachung = 6000 + + +class RP_Rechtsstand(XPlanungEnumMixin, Enum): + """ Rechtsstand des Plans """ + + def __new__(cls, *args, **kwds): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: int, version: XPlanVersion = None): + self._xplan_version = version + + @property + def version(self) -> XPlanVersion: + return self._xplan_version + + Aufstellungsbeschluss = 1000 + Entwurf = 2000 + EntwurfGenehmigt = 2001 + EntwurfGeaendert = 2002 + EntwurfAufgegeben = 2003 + EntwurfRuht = 2004 + Plan = 3000 + Inkraftgetreten = 4000 + AllgemeinePlanungsabsicht = 5000 + TeilweiseAusserKraft = 5500, XPlanVersion.SIX + AusserKraft = 6000 + PlanUngueltig = 7000 diff --git a/src/SAGisXPlanung/RPlan/RP_Basisobjekte/feature_types.py b/src/SAGisXPlanung/RPlan/RP_Basisobjekte/feature_types.py new file mode 100644 index 0000000..94e6901 --- /dev/null +++ b/src/SAGisXPlanung/RPlan/RP_Basisobjekte/feature_types.py @@ -0,0 +1,82 @@ +from qgis.PyQt.QtCore import Qt +from qgis.PyQt.QtGui import QColor +from qgis.core import QgsSymbol, QgsWkbTypes, QgsSimpleLineSymbolLayer, QgsSingleSymbolRenderer + +from sqlalchemy import Column, ForeignKey, Enum, String, Date, ARRAY, Integer +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from SAGisXPlanung.RPlan.RP_Basisobjekte.enums import RP_Art, RP_Rechtsstand, RP_Verfahren +from SAGisXPlanung.XPlan.enums import XP_Bundeslaender +from SAGisXPlanung.XPlan.feature_types import XP_Plan, XP_Bereich +from SAGisXPlanung.XPlan.types import GeometryType + + +class RP_Plan(XP_Plan): + """ Die Klasse RP_Plan modelliert einen Raumordnungsplan. """ + + def __init__(self): + self.auslegungsEndDatum = [] + self.auslegungsStartDatum = [] + self.traegerbeteiligungsStartDatum = [] + self.traegerbeteiligungsEndDatum = [] + + __tablename__ = 'rp_plan' + __mapper_args__ = { + 'polymorphic_identity': 'rp_plan', + } + + id = Column(ForeignKey("xp_plan.id", ondelete='CASCADE'), primary_key=True) + + bundesland = Column(Enum(XP_Bundeslaender), doc='Zuständiges Bundesland') + planArt = Column(Enum(RP_Art), nullable=False, doc='Art des Plans') + planungsregion = Column(Integer(), doc='Kennziffer der Planungsregion') + teilabschnitt = Column(Integer(), doc='Kennziffer des Teilabschnittes') + rechtsstand = Column(Enum(RP_Rechtsstand), doc='Rechtsstand') + aufstellungsbeschlussDatum = Column(Date(), doc='Aufstellungsbeschlussdatum') + auslegungsStartDatum = Column(ARRAY(Date), doc='Startdatum der Auslegung') + auslegungsEndDatum = Column(ARRAY(Date), doc='Enddatum der Auslegung') + traegerbeteiligungsStartDatum = Column(ARRAY(Date), doc='Start der Trägerbeteiligung') + traegerbeteiligungsEndDatum = Column(ARRAY(Date), doc='Ende der Trägerbeteiligung') + aenderungenBisDatum = Column(Date(), doc='Änderung bis') + entwurfsbeschlussDatum = Column(Date(), doc='Datum des Entwurfsbeschluss') + planbeschlussDatum = Column(Date(), doc='Datum des Planbeschlusses') + datumDesInkrafttretens = Column(Date(), doc='Datum des Inkrafttretens') + verfahren = Column(Enum(RP_Verfahren), doc='Verfahren') + amtlicherSchluessel = Column(String(), doc='Amtlicher Schlüssel') + genehmigungsbehoerde = Column(String(), doc='Genehmigungsbehörde') + bereich = relationship("RP_Bereich", back_populates="gehoertZuPlan", cascade="all, delete", doc='Bereich') + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.8) + line.setPenStyle(Qt.SolidLine) + + symbol.appendSymbolLayer(line) + return QgsSingleSymbolRenderer(symbol) + + +class RP_Bereich(XP_Bereich): + """ Die Klasse RP_Bereich modelliert einen Bereich eines Raumordnungsplans. """ + + __tablename__ = 'rp_bereich' + __mapper_args__ = { + 'polymorphic_identity': 'rp_bereich', + } + + id = Column(ForeignKey("xp_bereich.id", ondelete='CASCADE'), primary_key=True) + + versionBROG = Column(Date()) + versionBROGText = Column(String()) + versionLPLG = Column(Date()) + versionLPLGText = Column(String()) + geltungsmassstab = Column(Integer()) + + gehoertZuPlan_id = Column(UUID(as_uuid=True), ForeignKey('rp_plan.id', ondelete='CASCADE')) + gehoertZuPlan = relationship('RP_Plan', back_populates='bereich') + diff --git a/src/SAGisXPlanung/RPlan/__init__.py b/src/SAGisXPlanung/RPlan/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/RuleBasedSymbolRenderer.py b/src/SAGisXPlanung/RuleBasedSymbolRenderer.py new file mode 100644 index 0000000..50e56a2 --- /dev/null +++ b/src/SAGisXPlanung/RuleBasedSymbolRenderer.py @@ -0,0 +1,50 @@ +import os + +from qgis.core import (QgsSymbol, QgsWkbTypes, QgsCentroidFillSymbolLayer, QgsMarkerSymbol, QgsSvgMarkerSymbolLayer, + QgsUnitTypes, QgsMapUnitScale, QgsSymbolLayer, QgsProperty) +from qgis.core import QgsRuleBasedRenderer + + +class RuleBasedSymbolRenderer(QgsRuleBasedRenderer): + + def __init__(self, icon_map, base_symbology: QgsSymbol, category: str, symbol_size=12, + geometry_type=QgsWkbTypes.PolygonGeometry): + super().__init__(QgsSymbol.defaultSymbol(geometry_type)) + + root_rule = self.rootRule() + self.base_symbology = base_symbology + self.symbol_size = symbol_size + self.geometry_type = geometry_type + + for label, expression, svg_file in icon_map: + rule = root_rule.children()[0].clone() + rule.setLabel(label) + symbol = self.base_symbology.clone() + if svg_file: + rule.setFilterExpression(expression) + + path = os.path.abspath(os.path.join(os.path.dirname(__file__), f'symbole/{category}/{svg_file}')) + svg_symbol_layer = QgsSvgMarkerSymbolLayer(path, size=self.symbol_size) + size_prop = QgsProperty.fromExpression(f'"skalierung" * 2 * {self.symbol_size}') + angle_prop = QgsProperty.fromField("drehwinkel") + svg_symbol_layer.setDataDefinedProperty(QgsSymbolLayer.Property.PropertySize, size_prop) + svg_symbol_layer.setDataDefinedProperty(QgsSymbolLayer.Property.PropertyAngle, angle_prop) + svg_symbol_layer.setOutputUnit(QgsUnitTypes.RenderMapUnits) + if self.geometry_type == QgsWkbTypes.PointGeometry: + symbol.deleteSymbolLayer(0) + symbol.appendSymbolLayer(svg_symbol_layer) + else: + svg_marker = QgsCentroidFillSymbolLayer() + svg_marker.setPointOnSurface(True) + svg_symbol = QgsMarkerSymbol.createSimple({}) + svg_symbol.deleteSymbolLayer(0) + svg_symbol.appendSymbolLayer(svg_symbol_layer) + svg_marker.setSubSymbol(svg_symbol) + symbol.appendSymbolLayer(svg_marker) + else: + rule.setIsElse(True) + + rule.setSymbol(symbol) + root_rule.appendChild(rule) + + root_rule.removeChildAt(0) diff --git a/src/SAGisXPlanung/Settings.py b/src/SAGisXPlanung/Settings.py new file mode 100644 index 0000000..1cd00ea --- /dev/null +++ b/src/SAGisXPlanung/Settings.py @@ -0,0 +1,357 @@ +import asyncio +import functools +import logging +import os + +import asyncpg +import qasync +import yaml + +from qgis.core import QgsDataSourceUri, QgsProviderRegistry +from qgis.utils import iface + +from qgis.PyQt import uic +from qgis.PyQt.QtCore import pyqtSlot, QSettings, Qt, QTimer +from qgis.PyQt.QtGui import QIcon, QShowEvent, QCloseEvent +from qgis.PyQt.QtWidgets import QLineEdit, QDialog, QMessageBox, QPushButton +from sqlalchemy import create_engine, text +from sqlalchemy.exc import DatabaseError, DBAPIError +from sqlalchemy.ext.asyncio import create_async_engine + + +from SAGisXPlanung import Session, VERSION, COMPATIBLE_DB_REVISIONS, SessionAsync, BASE_DIR, XPlanVersion +from SAGisXPlanung.ext.spinner import WaitingSpinner +# don't remove following import: all classes need to be imported at plugin startup for ORM to work correctly +from SAGisXPlanung.gui.widgets import QAttributeConfigView + +FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'ui/settings.ui')) +logger = logging.getLogger(__name__) + + +class Settings(QDialog, FORM_CLASS): + def __init__(self, parent=None): + super(Settings, self).__init__(parent) + self.setupUi(self) + self.first_start = False + self.versionLabel.setText(VERSION) + + self.checkPath.stateChanged.connect(lambda state: self.tbPath.setEnabled((not bool(state)))) + + self.fillConnections() + + self.cbXPlanVersion.addItems([e.value for e in XPlanVersion]) + self.setXPlanVersion() + + self.status_label.hide() + + # page 2 - Attributes + self.attribute_view = QAttributeConfigView() + self.tabs.widget(1).layout().addWidget(self.attribute_view) + self.filter_edit.setPlaceholderText('Suchen...') + self.filter_edit.addAction(QIcon(':/images/themes/default/search.svg'), QLineEdit.LeadingPosition) + self.filter_edit.textChanged.connect(self.attribute_view.onFilterTextChanged) + + # page 3 - Database + self.tab_database_actions.tabBar().setCursor(Qt.PointingHandCursor) + self.tab_database_actions.setCurrentIndex(0) + self.db_create_options = [w for w in self.db_create_group.children() if isinstance(w, QLineEdit)] + self.db_create.setEnabled(False) + self.db_create.clicked.connect(self.onDatabaseCreateClicked) + for w in self.db_create_options: + w.textChanged.connect(self.db_create_options_changed) + + self.db_tab_spinner = WaitingSpinner(self.tab_database_actions, disableParentWhenSpinning=True, radius=5, + lines=20, line_length=5, line_width=1, color=(0, 6, 128)) + + self.tab_database_actions.setStyleSheet(''' + QToolButton { + border: 0px; + } + #category { + text-transform: uppercase; + font-weight: 400; + color: #374151; + } + #name { + font-size: 1.125rem; + font-weight: 500; + color: #1c1917; + } + #error_message{ + font-weight: bold; + font-size: 7pt; + color: #991B1B; + } + QTabWidget::pane { + border: none; + border-top: 1px solid #e5e7eb; + position: absolute; + } + /* Style the tab using the tab sub-control. Note that + it reads QTabBar _not_ QTabWidget */ + QTabBar::tab { + border: none; + min-width: 20ex; + padding: 15px; + color: #4b5563; + cursor: pointer; + } + + QTabBar::tab:hover { + background-color: #e5e7eb; + color: #111827; + } + + QTabBar::tab:selected { + border-bottom: 2px solid #93C5FD; + background-color: #eff6ff; + color: #111827; + } + ''') + + self.tabs.setCurrentIndex(0) + + def showEvent(self, e: QShowEvent): + super(Settings, self).showEvent(e) + self.fillConnections() + self.setXPlanVersion() + + self.attribute_view.setupModelData() + + def closeEvent(self, e: QCloseEvent): + super(Settings, self).closeEvent(e) + + self.status_label.setText('') + self.status_action.setText('') + + conf = self.attribute_view.config_dict() + s = QSettings() + s.setValue(f"plugins/xplanung/attribute_config", yaml.dump(conf, default_flow_style=False)) + + self.saveSettings() + + @qasync.asyncSlot() + async def onDatabaseCreateClicked(self): + self.db_tab_spinner.start() + + self.db_create.setEnabled(False) + db = self.db_name.text() + username = self.db_username.text() + password = self.db_password.text() + host = self.db_host.text() + port = self.db_port.text() + + self.status_label.show() + self.status_label.setText('Datenbank erstellen...') + self.status_action.setText('') + engine = create_async_engine(f"postgresql+asyncpg://{username}:{password}@{host}:{port}/postgres", + isolation_level='AUTOCOMMIT') + try: + async with engine.connect() as conn: + await conn.execute(text(f'CREATE DATABASE {db}')) + + self.status_label.setText('XPlanung Schema erstellen...') + + # TODO: sqlalchemy+asyncpg can not execute multiple queries (sql file), as it is always using a prepared + # statement, even if not required at all + # therefore creating schema currently needs to be run in an executor + # https://github.com/MagicStack/asyncpg/issues/1041 + # https://github.com/sqlalchemy/sqlalchemy/issues/6467 + def create_schema(): + engine = create_engine(f"postgresql://{username}:{password}@{host}:{port}/{db}") + with engine.begin() as conn: + with open(os.path.join(BASE_DIR, f'database/create_v{VERSION}.sql')) as file: + conn.execute(file.read()) + + loop = asyncio.get_event_loop() + await loop.run_in_executor(None, create_schema) + + except DBAPIError as e: + logger.exception(e) + # TODO: currently no way to access the original error message from asyncpg + # https://github.com/sqlalchemy/sqlalchemy/issues/8047 + # if e.orig == asyncpg.exceptions.DuplicateDatabaseError: + # self.status_label.setText(f'FEHLER: Datenbank »{db}« existiert bereits') + # return + self.status_label.setText(str(e)) + return + except Exception as e: + logger.exception(e) + self.status_label.setText(str(e)) + return + finally: + self.db_create.setEnabled(True) + self.db_tab_spinner.stop() + + uri = QgsDataSourceUri() + uri.setConnection(host, port, db, username, password) + + config = { + "saveUsername": True, + "savePassword": True, + "estimatedMetadata": True, + "metadataInDatabase": True, + } + + metadata = QgsProviderRegistry.instance().providerMetadata('postgres') + connection = metadata.createConnection(uri.uri(), config) + connection.store(f"SAGis XPlanung: {db}") + iface.browserModel().reload() + + self.status_label.setText('Datenbank erstellt...') + self.status_action.setText('Neue Konfiguration anwenden') + self.status_action.mousePressEvent = functools.partial(self.statusActionConfigApply, f"SAGis XPlanung: {db}") + + def statusActionConfigApply(self, new_connection_name: str, event): + QSettings().setValue(f"plugins/xplanung/connection", new_connection_name) + self.fillConnections() + + self.status_action.setText('') + self.status_label.setText(f'Neue Konfiguration {new_connection_name} ausgewählt.') + QTimer.singleShot(5000, lambda: self.status_label.setText('')) + + self.status_action.mousePressEvent = lambda _: None + self.tab_database_actions.setCurrentIndex(0) + + def db_create_options_changed(self, t): + self.db_create.setEnabled(not any(e.text() == '' for e in self.db_create_options)) + + def setXPlanVersion(self): + s = QSettings() + default_version = s.value(f"plugins/xplanung/export_version", '') + index = self.cbXPlanVersion.findText(str(default_version)) + if index >= 0: + self.cbXPlanVersion.setCurrentIndex(index) + + def fillConnections(self): + self.cbConnections.clear() + + qs = QSettings() + qs.beginGroup("PostgreSQL/connections") + conn_names = [cn for cn in qs.childGroups()] + qs.endGroup() + + self.cbConnections.addItems(conn_names) + self.cbConnections.currentIndexChanged.connect(self.connIndexChanged) + + self.first_start = qs.contains(f"plugins/xplanung/connection") + + index = 0 + for conn in conn_names: + if conn == qs.value(f"plugins/xplanung/connection"): + index = self.cbConnections.findText(conn) + break + + if index >= 0: + self.cbConnections.setCurrentIndex(index) + elif conn_names: + self.cbConnections.setCurrentIndex(0) + self.connIndexChanged() + + @pyqtSlot() + def connIndexChanged(self): + conn_name = str(self.cbConnections.currentText()) + qs = QSettings() + username = qs.value(f"PostgreSQL/connections/{conn_name}/username", '') + password = qs.value(f"PostgreSQL/connections/{conn_name}/password", '') + self.tbUsername.setText(username) + self.tbPassword.setText(password) + + def saveSettings(self): + conn_name = str(self.cbConnections.currentText()) + qs = QSettings() + qs.setValue(f"plugins/xplanung/export_version", self.cbXPlanVersion.currentText()) + if self.checkPath.isChecked(): + qs.setValue(f"plugins/xplanung/export_path", '') + else: + qs.setValue(f"plugins/xplanung/export_path", self.tbPath.text()) + qs.setValue(f"plugins/xplanung/connection", conn_name) + qs.setValue(f"PostgreSQL/connections/{conn_name}/username", self.tbUsername.text()) + qs.setValue(f"PostgreSQL/connections/{conn_name}/password", self.tbPassword.text()) + configureSession() + self.accept() + + @staticmethod + def connectionParams(): + qs = QSettings() + conn_name = qs.value(f"plugins/xplanung/connection") + return { + "username": qs.value(f"PostgreSQL/connections/{conn_name}/username"), + "password": qs.value(f"PostgreSQL/connections/{conn_name}/password"), + "host": qs.value(f"PostgreSQL/connections/{conn_name}/host"), + "port": qs.value(f"PostgreSQL/connections/{conn_name}/port"), + "db": qs.value(f"PostgreSQL/connections/{conn_name}/database") + } + + +def configureSession(): + try: + conn = Settings.connectionParams() + db_str = f"postgresql://{conn['username']}:{conn['password']}@{conn['host']}:{conn['port']}/{conn['db']}" + engine = create_engine(db_str) + Session.configure(bind=engine) + + async_db_str = f"postgresql+asyncpg://{conn['username']}:{conn['password']}@{conn['host']}:{conn['port']}/{conn['db']}" + async_engine = create_async_engine(async_db_str) + SessionAsync.configure(bind=async_engine) + return True + except Exception as ex: + logger.exception(f'Error on session config: {ex}') + return False + + +def tryConnect(): + conn = Settings.connectionParams() + db_str = f"postgresql://{conn['username']}:{conn['password']}@{conn['host']}:{conn['port']}/{conn['db']}" + ngn = create_engine(db_str) + ngn.connect() + + +def checkDbConnection(): + try: + tryConnect() + return True + except Exception: + msgBox = QMessageBox() + msgBox.setWindowTitle('Verbindungsfehler') + msgBox.setIcon(QMessageBox.Warning) + msgBox.setText('Keine Verbindung mit der Datenbank möglich') + msgBox.addButton(QPushButton('Einstellungen'), QMessageBox.YesRole) + msgBox.addButton(QMessageBox.Ok) + ret = msgBox.exec_() + if not ret: + Settings().exec_() + return checkDbConnection() + + +def is_valid_db(): + if not checkDbConnection(): + return False + + ngn = Session().get_bind() + with ngn.connect() as conn: + try: + res = conn.execute(text('SELECT version_num FROM alembic_version')) + db_version_row = res.first() + if db_version_row and db_version_row[0] in COMPATIBLE_DB_REVISIONS: + return True + + msg = f'Die Version der QGIS-Erweiterung ist inkompatibel mit der aktuell angegebenen Datenbank!
      ' + msg += f"
    • Aktuelle Datenbankversion: {db_version_row[0]}
    • " + msg += f"
    • Erwartete Datenbankversion: {', '.join(COMPATIBLE_DB_REVISIONS)}
    • " + msg += '
    ' + except DatabaseError as e: + msg = f'Die angegebene Datenbank ist nicht kompatibel mit SAGis XPlanung! Bitte konfigurieren Sie' \ + f' eine neue Verbindung in den Einstellungen.' + + msgBox = QMessageBox() + msgBox.setWindowTitle('Inkompatiblität mit der Datenbank') + msgBox.setIcon(QMessageBox.Warning) + msgBox.setText(msg) + settings_button = msgBox.addButton('Einstellungen', QMessageBox.YesRole) + msgBox.addButton(QMessageBox.Cancel) + ret = msgBox.exec() + if msgBox.clickedButton() == settings_button: + Settings().exec() + return False + diff --git a/src/SAGisXPlanung/SonstigePlanwerke/SO_Basisobjekte/__init__.py b/src/SAGisXPlanung/SonstigePlanwerke/SO_Basisobjekte/__init__.py new file mode 100644 index 0000000..907be2d --- /dev/null +++ b/src/SAGisXPlanung/SonstigePlanwerke/SO_Basisobjekte/__init__.py @@ -0,0 +1,3 @@ + +from .enums import SO_Rechtscharakter +from .feature_types import SO_Objekt diff --git a/src/SAGisXPlanung/SonstigePlanwerke/SO_Basisobjekte/enums.py b/src/SAGisXPlanung/SonstigePlanwerke/SO_Basisobjekte/enums.py new file mode 100644 index 0000000..7463c6e --- /dev/null +++ b/src/SAGisXPlanung/SonstigePlanwerke/SO_Basisobjekte/enums.py @@ -0,0 +1,39 @@ +from enum import Enum + +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin + + +class SO_Rechtscharakter(XPlanungEnumMixin, Enum): + """ Rechtscharakter des Planinhalts """ + + FestsetzungBPlan = 1000 + DarstellungFPlan = 1500 + InhaltLPlan = 1800 + NachrichtlicheUebernahme = 2000 + Hinweis = 3000 + Vermerk = 4000 + Kennzeichnung = 5000 + Unbekannt = 9998 + Sonstiges = 9999 + + def to_xp_rechtscharakter(self): + from SAGisXPlanung.XPlan.enums import XP_Rechtscharakter + + if self.value == 1000: + return XP_Rechtscharakter.FestsetzungBPlan + if self.value == 1500: + return XP_Rechtscharakter.DarstellungFPlan + if self.value == 1800: + return XP_Rechtscharakter.FestsetzungImLP + elif self.value == 2000: + return XP_Rechtscharakter.NachrichtlicheUebernahme + elif self.value == 3000: + return XP_Rechtscharakter.Hinweis + elif self.value == 4000: + return XP_Rechtscharakter.Vermerk + elif self.value == 5000: + return XP_Rechtscharakter.Kennzeichnung + elif self.value == 9998: + return XP_Rechtscharakter.Unbekannt + elif self.value == 9999: + return XP_Rechtscharakter.Sonstiges diff --git a/src/SAGisXPlanung/SonstigePlanwerke/SO_Basisobjekte/feature_types.py b/src/SAGisXPlanung/SonstigePlanwerke/SO_Basisobjekte/feature_types.py new file mode 100644 index 0000000..7814011 --- /dev/null +++ b/src/SAGisXPlanung/SonstigePlanwerke/SO_Basisobjekte/feature_types.py @@ -0,0 +1,53 @@ + +from geoalchemy2 import Geometry, WKBElement +from sqlalchemy import Column, ForeignKey, Enum, Boolean, Float + +from qgis.core import QgsCoordinateReferenceSystem, QgsGeometry + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.GML.geometry import geometry_from_spatial_element +from SAGisXPlanung.SonstigePlanwerke.SO_Basisobjekte import SO_Rechtscharakter +from SAGisXPlanung.XPlan.conversions import SO_Rechtscharakter_EnumType +from SAGisXPlanung.XPlan.core import XPCol +from SAGisXPlanung.XPlan.feature_types import XP_Objekt +from SAGisXPlanung.XPlan.types import Angle, GeometryType + + +class SO_Objekt(XP_Objekt): + """ Basisklasse für die Inhalte sonstiger raumbezogener Planwerke ,von Klassen zur Modellierung nachrichtlicher + Übernahmen, sowie Planart-übergreifende Planinhalte """ + + __tablename__ = 'so_objekt' + __mapper_args__ = { + 'polymorphic_identity': 'so_objekt', + } + __readonly_columns__ = ['position'] + + id = Column(ForeignKey("xp_objekt.id", ondelete='CASCADE'), primary_key=True) + + rechtscharakter = XPCol(SO_Rechtscharakter_EnumType(SO_Rechtscharakter), nullable=False, doc='Rechtscharakter', + version=XPlanVersion.FIVE_THREE) + + position = Column(Geometry()) + flaechenschluss = Column(Boolean, doc='Flächenschluss') + flussrichtung = Column(Boolean) + nordwinkel = Column(Float) + + def srs(self): + return QgsCoordinateReferenceSystem(f'EPSG:{self.position.srid}') + + def geometry(self): + return geometry_from_spatial_element(self.position) + + def setGeometry(self, geom: QgsGeometry, srid: int = None): + if srid is None and self.position is None: + raise Exception('geometry needs a srid') + self.position = WKBElement(geom.asWkb(), srid=srid or self.position.srid) + + def geomType(self) -> GeometryType: + return self.geometry().type() + + @classmethod + def hidden_inputs(cls): + h = super(SO_Objekt, cls).hidden_inputs() + return h + ['position'] diff --git a/src/SAGisXPlanung/SonstigePlanwerke/SO_NachrichtlicheUebernahmen/__init__.py b/src/SAGisXPlanung/SonstigePlanwerke/SO_NachrichtlicheUebernahmen/__init__.py new file mode 100644 index 0000000..c690208 --- /dev/null +++ b/src/SAGisXPlanung/SonstigePlanwerke/SO_NachrichtlicheUebernahmen/__init__.py @@ -0,0 +1,3 @@ + +from .enums import SO_KlassifizNachSchienenverkehrsrecht, SO_KlassifizNachDenkmalschutzrecht +from .feature_types import SO_Denkmalschutzrecht, SO_Schienenverkehrsrecht \ No newline at end of file diff --git a/src/SAGisXPlanung/SonstigePlanwerke/SO_NachrichtlicheUebernahmen/data_types.py b/src/SAGisXPlanung/SonstigePlanwerke/SO_NachrichtlicheUebernahmen/data_types.py new file mode 100644 index 0000000..a15392c --- /dev/null +++ b/src/SAGisXPlanung/SonstigePlanwerke/SO_NachrichtlicheUebernahmen/data_types.py @@ -0,0 +1,52 @@ +from uuid import uuid4 + +from sqlalchemy import Column, Enum, String, ForeignKey +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from SAGisXPlanung import Base +from SAGisXPlanung.SonstigePlanwerke.SO_NachrichtlicheUebernahmen.enums import SO_ZweckbestimmungStrassenverkehr, \ + SO_KlassifizGewaesser +from SAGisXPlanung.XPlan.mixins import RelationshipMixin, ElementOrderMixin + + +class SO_KomplexeZweckbestStrassenverkehr(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung einer Fläche oder Anlage für den Strassenverkehr. """ + + __tablename__ = 'so_zweckbestimmung_strassenverkehr' + __avoidRelation__ = ['strassenverkehr'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(SO_ZweckbestimmungStrassenverkehr), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + strassenverkehr_id = Column(UUID(as_uuid=True), ForeignKey('so_strassenverkehr.id', ondelete='CASCADE')) + strassenverkehr = relationship('SO_Strassenverkehr', back_populates='artDerFestlegung') + + @classmethod + def avoid_export(cls): + return ['strassenverkehr_id'] + + +class SO_KomplexeFestlegungGewaesser(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Zweckbestimmung der Fläche. """ + + __tablename__ = 'so_festlegung_gewaesser' + __avoidRelation__ = ['gewaesser'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + allgemein = Column(Enum(SO_KlassifizGewaesser), nullable=False) + + textlicheErgaenzung = Column(String) + aufschrift = Column(String) + + gewaesser_id = Column(UUID(as_uuid=True), ForeignKey('so_gewaesser.id', ondelete='CASCADE')) + gewaesser = relationship('SO_Gewaesser', back_populates='artDerFestlegung') + + @classmethod + def avoid_export(cls): + return ['gewaesser_id'] diff --git a/src/SAGisXPlanung/SonstigePlanwerke/SO_NachrichtlicheUebernahmen/enums.py b/src/SAGisXPlanung/SonstigePlanwerke/SO_NachrichtlicheUebernahmen/enums.py new file mode 100644 index 0000000..ec25fb8 --- /dev/null +++ b/src/SAGisXPlanung/SonstigePlanwerke/SO_NachrichtlicheUebernahmen/enums.py @@ -0,0 +1,114 @@ +from enum import Enum + +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin + + +class SO_KlassifizNachSchienenverkehrsrecht(XPlanungEnumMixin, Enum): + """ Rechtliche Klassifizierung der Festlegung nach Schienenverkehrsrecht""" + + Bahnanlage = 1000 + DB_Bahnanlage = 10000 + Personenbahnhof = 10001 + Fernbahnhof = 10002 + Gueterbahnhof = 10003 + Bahnlinie = 1200 + Personenbahnlinie = 12000 + Regionalbahn = 12001 + Kleinbahn = 12002 + Gueterbahnlinie = 12003 + WerksHafenbahn = 12004 + Seilbahn = 12005 + OEPNV = 1400 + Strassenbahn = 14000 + UBahn = 14001 + SBahn = 14002 + OEPNV_Haltestelle = 14003 + Sonstiges = 9999 + + +class SO_KlassifizNachDenkmalschutzrecht(XPlanungEnumMixin, Enum): + """ Rechtliche Klassifizierung der Festlegung nach Denkmalschutzrecht""" + + DenkmalschutzEnsemble = 1000 + DenkmalschutzEinzelanlage = 1100 + Grabungsschutzgebiet = 1200 + PufferzoneWeltkulturerbeEnger = 1300 + PufferzoneWeltkulturerbeWeiter = 1400 + ArcheologischesDenkmal = 1500 + Bodendenkmal = 1600 + Sonstiges = 9999 + + +class SO_ZweckbestimmungStrassenverkehr(XPlanungEnumMixin, Enum): + """ Allgemeine Zweckbestimmung der Fläche oder Anlage für Straßenverkehr""" + + AutobahnUndAehnlich = 1000 + Hauptverkehrsstrasse = 1200 + SonstigerVerkehrswegAnlage = 1400 + VerkehrsberuhigterBereich = 14000 + Platz = 14001 + Fussgaengerbereich = 14002 + RadGehweg = 14003 + Radweg = 14004 + Gehweg = 14005 + Wanderweg = 14006 + ReitKutschweg = 14007 + Rastanlage = 14008 + Busbahnhof = 14009 + UeberfuehrenderVerkehrsweg = 140010 + UnterfuehrenderVerkehrsweg = 140011 + Wirtschaftsweg = 140012 + LandwirtschaftlicherVerkehr = 140013 + Anschlussflaeche = 14014 + Verkehrsgruen = 14015 + RuhenderVerkehr = 1600 + Parkplatz = 16000 + FahrradAbstellplatz = 16001 + P_RAnlage = 16002 + B_RAnlage = 16003 + Parkhaus = 16004 + CarSharing = 16005 + BikeSharing = 16006 + Mischverkehrsflaeche = 3400 + Ladestation = 3500 + Sonstiges = 9999 + + +class SO_StrassenEinteilung(XPlanungEnumMixin, Enum): + """ Straßeneinteilung nach Bundes-Fernstraßengesetz """ + + Bundesautobahn = 1000 + Bundesstrasse = 1100 + LandesStaatsstrasse = 1200 + Kreisstrasse = 1300 + Gemeindestrasse = 1400 + SonstOeffentlStrasse = 9999 + + +class SO_KlassifizGewaesser(XPlanungEnumMixin, Enum): + """ Allgemeine Zweckbestimmung einer Gewaesserfläche """ + + Gewaesser = 1000 + Fliessgewaesser = 2000 + Gewaesser1Ordnung = 20000 + Gewaesser2Ordnung = 20001 + Gewaesser3Ordnung = 20002 + StehendesGewaesser = 3000 + Hafen = 4000 + Sportboothafen = 40000 + Wasserstrasse = 5000 + Kanal = 6000 + Sonstiges = 9999 + + +class SO_KlassifizWasserwirtschaft(XPlanungEnumMixin, Enum): + """ Klassifizierung der Festlegung einer Fläche für Wasserwirtschaft """ + + HochwasserRueckhaltebecken = 1000 + Ueberschwemmgebiet = 1100 + Versickerungsflaeche = 1200 + Entwaesserungsgraben = 1300 + Deich = 1400 + RegenRueckhaltebecken = 1500 + Sonstiges = 9999 + diff --git a/src/SAGisXPlanung/SonstigePlanwerke/SO_NachrichtlicheUebernahmen/feature_types.py b/src/SAGisXPlanung/SonstigePlanwerke/SO_NachrichtlicheUebernahmen/feature_types.py new file mode 100644 index 0000000..9079215 --- /dev/null +++ b/src/SAGisXPlanung/SonstigePlanwerke/SO_NachrichtlicheUebernahmen/feature_types.py @@ -0,0 +1,294 @@ +from qgis.PyQt.QtCore import Qt, QSize +from qgis.PyQt.QtGui import QColor +from qgis.core import (QgsSymbol, QgsSimpleFillSymbolLayer, QgsSimpleLineSymbolLayer, QgsUnitTypes, QgsWkbTypes, Qgis, + QgsSingleSymbolRenderer, QgsSymbolLayerUtils, QgsMarkerLineSymbolLayer, QgsMarkerSymbol, + QgsSimpleMarkerSymbolLayer, QgsSimpleMarkerSymbolLayerBase) + +from sqlalchemy import Column, ForeignKey, Enum, String, Boolean, Integer, Float, ARRAY +from sqlalchemy.orm import relationship + +from SAGisXPlanung.RuleBasedSymbolRenderer import RuleBasedSymbolRenderer +from SAGisXPlanung.SonstigePlanwerke.SO_Basisobjekte import SO_Objekt +from SAGisXPlanung.SonstigePlanwerke.SO_NachrichtlicheUebernahmen import (SO_KlassifizNachSchienenverkehrsrecht, + SO_KlassifizNachDenkmalschutzrecht) +from SAGisXPlanung.SonstigePlanwerke.SO_NachrichtlicheUebernahmen.enums import SO_ZweckbestimmungStrassenverkehr, \ + SO_StrassenEinteilung, SO_KlassifizWasserwirtschaft +from SAGisXPlanung.XPlan.enums import XP_Nutzungsform +from SAGisXPlanung.XPlan.mixins import PolygonGeometry, MixedGeometry +from SAGisXPlanung.XPlan.types import GeometryType, Area, Length, Volume, XPEnum + + +class SO_Schienenverkehrsrecht(PolygonGeometry, SO_Objekt): + """ Festlegung nach Schienenverkehrsrecht """ + + __tablename__ = 'so_schienenverkehr' + __mapper_args__ = { + 'polymorphic_identity': 'so_schienenverkehr', + } + + id = Column(ForeignKey("so_objekt.id", ondelete='CASCADE'), primary_key=True) + + artDerFestlegung = Column(Enum(SO_KlassifizNachSchienenverkehrsrecht)) + name = Column(String) + nummer = Column(String) + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#b69ad1')) + symbol.appendSymbolLayer(fill) + + line = QgsSimpleLineSymbolLayer.create({}) + line.setColor(QColor(0, 0, 0)) + line.setWidth(0.5) + line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + line.setPenStyle(Qt.SolidLine) + symbol.appendSymbolLayer(line) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(48, 48)) + + +class SO_Denkmalschutzrecht(PolygonGeometry, SO_Objekt): + """ Festlegung nach Denkmalschutzrecht """ + + __tablename__ = 'so_denkmalschutz' + __mapper_args__ = { + 'polymorphic_identity': 'so_denkmalschutz', + } + + id = Column(ForeignKey("so_objekt.id", ondelete='CASCADE'), primary_key=True) + + artDerFestlegung = Column(Enum(SO_KlassifizNachDenkmalschutzrecht)) + weltkulturerbe = Column(Boolean) + name = Column(String) + nummer = Column(String) + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + if Qgis.versionInt() >= 32400: + shape = Qgis.MarkerShape.Square + else: + shape = QgsSimpleMarkerSymbolLayerBase.Square + symbol_layer = QgsSimpleMarkerSymbolLayer(shape=shape, color=QColor('#ef0000'), size=2) + symbol_layer.setOutputUnit(QgsUnitTypes.RenderMapUnits) + + marker_line = QgsMarkerLineSymbolLayer(interval=3) + marker_line.setOffset(0.8) + marker_line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + marker_symbol = QgsMarkerSymbol() + marker_symbol.deleteSymbolLayer(0) + marker_symbol.appendSymbolLayer(symbol_layer) + marker_line.setSubSymbol(marker_symbol) + + symbol.appendSymbolLayer(marker_line) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(64, 64)) + + +class SO_Strassenverkehr(MixedGeometry, SO_Objekt): + """ + Verkehrsfläche besonderer Zweckbestimmung (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB), Darstellung von Flächen für den + überörtlichen Verkehr und für die örtlichen Hauptverkehrszüge ( §5, Abs. 2, Nr. 3 BauGB) sowie Festlegung nach + Straßenverkehrsrecht + """ + + __tablename__ = 'so_strassenverkehr' + __mapper_args__ = { + 'polymorphic_identity': 'so_strassenverkehr', + } + + id = Column(ForeignKey("so_objekt.id", ondelete='CASCADE'), primary_key=True) + + MaxZahlWohnungen = Column(Integer) + MinGRWohneinheit = Column(Area) + Fmin = Column(Area) + Fmax = Column(Area) + Bmin = Column(Length) + Bmax = Column(Length) + Tmin = Column(Length) + Tmax = Column(Length) + GFZmin = Column(Float) + GFZmax = Column(Float) + GFZ = Column(Float) + GFZ_Ausn = Column(Float) + GFmin = Column(Area) + GFmax = Column(Area) + GF = Column(Area) + GF_Ausn = Column(Area) + BMZ = Column(Float) + BMZ_Ausn = Column(Float) + BM = Column(Volume) + BM_Ausn = Column(Volume) + GRZmin = Column(Float) + GRZmax = Column(Float) + GRZ = Column(Float) + GRZ_Ausn = Column(Float) + GRmin = Column(Area) + GRmax = Column(Area) + GR = Column(Area) + GR_Ausn = Column(Area) + Zmin = Column(Integer) + Zmax = Column(Integer) + Zzwingend = Column(Integer) + Z = Column(Integer) + Z_Ausn = Column(Integer) + Z_Staffel = Column(Integer) + Z_Dach = Column(Integer) + ZUmin = Column(Integer) + ZUmax = Column(Integer) + ZUzwingend = Column(Integer) + ZU = Column(Integer) + ZU_Ausn = Column(Integer) + + artDerFestlegung = relationship("SO_KomplexeZweckbestStrassenverkehr", back_populates="strassenverkehr", + cascade="all, delete", passive_deletes=True) + einteilung = Column(XPEnum(SO_StrassenEinteilung, include_default=True)) + name = Column(String) + nummer = Column(String) + istOrtsdurchfahrt = Column(Boolean) + nutzungsform = Column(Enum(XP_Nutzungsform), nullable=False) + zugunstenVon = Column(String) + hatDarstellungMitBesondZweckbest = Column(Boolean, nullable=False) + + @classmethod + def polygon_symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#fbdd19')) + + symbol.appendSymbolLayer(fill) + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + if geom_type == QgsWkbTypes.PolygonGeometry: + return QgsSingleSymbolRenderer(cls.polygon_symbol()) + elif geom_type is not None: + return QgsSingleSymbolRenderer(QgsSymbol.defaultSymbol(geom_type)) + raise Exception('parameter geometryType should not be None') + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.polygon_symbol(), QSize(16, 16)) + + +class SO_Gewaesser(MixedGeometry, SO_Objekt): + """ Planartübergreifende Klasse zur Abbildung von Gewässern.""" + + __tablename__ = 'so_gewaesser' + __mapper_args__ = { + 'polymorphic_identity': 'so_gewaesser', + } + + id = Column(ForeignKey("so_objekt.id", ondelete='CASCADE'), primary_key=True) + + artDerFestlegung = relationship("SO_KomplexeFestlegungGewaesser", back_populates="gewaesser", + cascade="all, delete", passive_deletes=True) + + name = Column(String) + nummer = Column(String) + + @classmethod + def polygon_symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#c1dfea')) + symbol.appendSymbolLayer(fill) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + if geom_type == QgsWkbTypes.PolygonGeometry: + return QgsSingleSymbolRenderer(cls.polygon_symbol()) + elif geom_type is not None: + return QgsSingleSymbolRenderer(QgsSymbol.defaultSymbol(geom_type)) + raise Exception('parameter geometryType should not be None') + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.polygon_symbol(), QSize(16, 16)) + + +class SO_Wasserwirtschaft(MixedGeometry, SO_Objekt): + """ + Flächen für die Wasserwirtschaft, sowie Flächen für Hochwasserschutzanlagen und für die Regelung des + Wasserabflusses (§9 Abs. 1 Nr. 16a und 16b BauGB, §5 Abs. 2 Nr. 7 BauGB). + """ + + __tablename__ = 'so_wasserwirtschaft' + __mapper_args__ = { + 'polymorphic_identity': 'so_wasserwirtschaft', + } + + id = Column(ForeignKey("so_objekt.id", ondelete='CASCADE'), primary_key=True) + + artDerFestlegung = Column(XPEnum(SO_KlassifizWasserwirtschaft, include_default=True)) + + __icon_map__ = [ + ('Hochwasserrückhaltebecken', '"artDerFestlegung" LIKE \'HochwasserRueckhaltebecken\'', 'Hochwasserrueckhaltebecken.svg'), + ('Überschwemmungsgebiet', '"artDerFestlegung" LIKE \'Ueberschwemmgebiet\'', 'Ueberschwemmungsgebiet.svg'), + ('Sonstiges', '"zweckbestimmung" LIKE \'\'', ''), + ] + + def layer_fields(self): + return { + 'artDerFestlegung': self.artDerFestlegung.name if self.artDerFestlegung else '', + 'skalierung': self.skalierung if self.skalierung else '', + 'drehwinkel': self.drehwinkel if self.drehwinkel else '' + } + + @classmethod + def attributes(cls): + return ['artDerFestlegung', 'skalierung', 'drehwinkel'] + + @classmethod + def polygon_symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + fill = QgsSimpleFillSymbolLayer(QColor('#ffffff')) + symbol.appendSymbolLayer(fill) + + blue_strip = QgsSimpleLineSymbolLayer(QColor('#45a1d0')) + blue_strip.setWidth(5) + blue_strip.setOffset(2.5) + blue_strip.setOutputUnit(QgsUnitTypes.RenderMapUnits) + blue_strip.setPenJoinStyle(Qt.MiterJoin) + symbol.appendSymbolLayer(blue_strip) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + if geom_type == QgsWkbTypes.PolygonGeometry: + return RuleBasedSymbolRenderer(cls.__icon_map__, cls.polygon_symbol(), 'BP_Wasser', symbol_size=20) + elif geom_type is not None: + return QgsSingleSymbolRenderer(QgsSymbol.defaultSymbol(geom_type)) + raise Exception('parameter geometryType should not be None') + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.polygon_symbol(), QSize(16, 16)) \ No newline at end of file diff --git a/src/SAGisXPlanung/SonstigePlanwerke/SO_Schutzgebiete/__init__.py b/src/SAGisXPlanung/SonstigePlanwerke/SO_Schutzgebiete/__init__.py new file mode 100644 index 0000000..fa07f6a --- /dev/null +++ b/src/SAGisXPlanung/SonstigePlanwerke/SO_Schutzgebiete/__init__.py @@ -0,0 +1,3 @@ + +from .enums import SO_KlassifizSchutzgebietWasserrecht, SO_SchutzzonenWasserrecht +from .feature_types import SO_SchutzgebietWasserrecht diff --git a/src/SAGisXPlanung/SonstigePlanwerke/SO_Schutzgebiete/enums.py b/src/SAGisXPlanung/SonstigePlanwerke/SO_Schutzgebiete/enums.py new file mode 100644 index 0000000..622a73e --- /dev/null +++ b/src/SAGisXPlanung/SonstigePlanwerke/SO_Schutzgebiete/enums.py @@ -0,0 +1,25 @@ +from enum import Enum + +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin + + +class SO_SchutzzonenWasserrecht(XPlanungEnumMixin, Enum): + """ Klassifizierung der Schutzzone im WSG """ + + Zone_1 = 1000 + Zone_2 = 1100 + Zone_3 = 1200 + Zone_3a = 1300 + Zone_3b = 1400 + Zone_4 = 1500 + + +class SO_KlassifizSchutzgebietWasserrecht(XPlanungEnumMixin, Enum): + """ Klassifizierung des Schutzgebietes im WSG """ + + Wasserschutzgebiet = 1000 + QuellGrundwasserSchutzgebiet = 10000 + OberflaechengewaesserSchutzgebiet = 10001 + Heilquellenschutzgebiet = 2000 + Sonstiges = 9999 + diff --git a/src/SAGisXPlanung/SonstigePlanwerke/SO_Schutzgebiete/feature_types.py b/src/SAGisXPlanung/SonstigePlanwerke/SO_Schutzgebiete/feature_types.py new file mode 100644 index 0000000..bc1d58d --- /dev/null +++ b/src/SAGisXPlanung/SonstigePlanwerke/SO_Schutzgebiete/feature_types.py @@ -0,0 +1,52 @@ +from qgis.PyQt.QtCore import Qt, QSize +from qgis.PyQt.QtGui import QColor +from qgis.core import (QgsSymbol, QgsSimpleFillSymbolLayer, QgsSimpleLineSymbolLayer, QgsUnitTypes, QgsWkbTypes, + QgsSingleSymbolRenderer, QgsSymbolLayerUtils, QgsMarkerLineSymbolLayer, QgsMarkerSymbol, + QgsSimpleMarkerSymbolLayer, QgsSimpleMarkerSymbolLayerBase) + +from sqlalchemy import Column, ForeignKey, Enum, String, Boolean + +from SAGisXPlanung.SonstigePlanwerke.SO_Basisobjekte import SO_Objekt +from SAGisXPlanung.SonstigePlanwerke.SO_Schutzgebiete import SO_KlassifizSchutzgebietWasserrecht, SO_SchutzzonenWasserrecht +from SAGisXPlanung.XPlan.mixins import PolygonGeometry +from SAGisXPlanung.XPlan.types import GeometryType + + +class SO_SchutzgebietWasserrecht(PolygonGeometry, SO_Objekt): + """ Schutzgebiet nach WasserSchutzGesetz (WSG) bzw. HeilQuellenSchutzGesetz (HQSG). """ + + __tablename__ = 'so_wasserschutz' + __mapper_args__ = { + 'polymorphic_identity': 'so_wasserschutz', + } + + id = Column(ForeignKey("so_objekt.id", ondelete='CASCADE'), primary_key=True) + + artDerFestlegung = Column(Enum(SO_KlassifizSchutzgebietWasserrecht)) + zone = Column(Enum(SO_SchutzzonenWasserrecht)) + name = Column(String) + nummer = Column(String) + + @classmethod + def symbol(cls) -> QgsSymbol: + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + symbol.setOpacity(0.7) + + symbol = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) + symbol.deleteSymbolLayer(0) + + line = QgsSimpleLineSymbolLayer(color=QColor('#00ffff'), width=25) + line.setOutputUnit(QgsUnitTypes.RenderMapUnits) + line.setDrawInsidePolygon(True) + symbol.appendSymbolLayer(line) + + return symbol + + @classmethod + def renderer(cls, geom_type: GeometryType = None): + return QgsSingleSymbolRenderer(cls.symbol()) + + @classmethod + def previewIcon(cls): + return QgsSymbolLayerUtils.symbolPreviewIcon(cls.symbol(), QSize(48, 48)) \ No newline at end of file diff --git a/src/SAGisXPlanung/SonstigePlanwerke/__init__.py b/src/SAGisXPlanung/SonstigePlanwerke/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/Tools/ContextMenuTool.py b/src/SAGisXPlanung/Tools/ContextMenuTool.py new file mode 100644 index 0000000..bcafd3d --- /dev/null +++ b/src/SAGisXPlanung/Tools/ContextMenuTool.py @@ -0,0 +1,207 @@ +import functools +import logging +import os +from enum import Enum +from typing import Union + +from qgis.gui import QgsMapToolIdentify, QgsMapCanvas, QgsHighlight, QgsMapMouseEvent +from qgis.core import (QgsIdentifyContext, QgsFeature, QgsMapLayer, QgsWkbTypes, QgsRectangle, QgsRenderedItemResults, + QgsRenderedAnnotationItemDetails, QgsProject, QgsAnnotationLayer, QgsAnnotationItem, + QgsAnnotationPointTextItem, QgsGeometry, QgsTextBackgroundSettings) +from qgis.utils import iface +from qgis.PyQt.QtWidgets import QMenu +from qgis.PyQt.QtCore import pyqtSignal, QPoint +from qgis.PyQt.QtGui import QIcon, QColor, QTransform + +from SAGisXPlanung import BASE_DIR +from SAGisXPlanung.BuildingTemplateItem import BuildingTemplateItem +from SAGisXPlanung.XPlan.feature_types import XP_Objekt +from SAGisXPlanung.XPlan.mixins import PolygonGeometry +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui import CreateAnnotateDialog +from SAGisXPlanung.gui.actions import EditBuildingTemplateAction, MoveAnnotationItemAction +from SAGisXPlanung.gui.widgets import QBuildingTemplateEdit +from SAGisXPlanung.utils import CLASSES, full_version_required_warning + +logger = logging.getLogger(__name__) + + +class ActionType(Enum): + AddPlanContent = 1 + AccessAttributes = 2 + AddAnnotationItem = 3 + HighlightObjectTreeItem = 4 + + +class ContextMenuTool(QgsMapToolIdentify): + editBuildingTemplateRequested = pyqtSignal(QBuildingTemplateEdit) + accessAttributesRequested = pyqtSignal(XPlanungItem) + highlightObjectTreeRequested = pyqtSignal(XPlanungItem) + featureSaved = pyqtSignal(XPlanungItem) + + def __init__(self, canvas: QgsMapCanvas, parent): + self.canvas = canvas + self.iface = iface + self.highlight = None + + super(ContextMenuTool, self).__init__(self.canvas) + + self.setParent(parent) + + def activate(self): + iface.mainWindow().statusBar().showMessage('Feature auf der Karte auswählen') + super(ContextMenuTool, self).activate() + + def deactivate(self): + iface.mainWindow().statusBar().clearMessage() + super(ContextMenuTool, self).deactivate() + + def menu(self, identify_results, canvas_item=None, annotation_items=None) -> QMenu: + menu = QMenu() + menu.aboutToHide.connect(self.delete_highlight) + + # identified canvas items (e.g. XP_Nutzungsschablone) + if canvas_item: + canvas_item_menu = menu.addMenu(QIcon(os.path.join(BASE_DIR, 'gui/resources/table.svg')), canvas_item.xtype) + edit_canvas_item_action = EditBuildingTemplateAction(canvas_item.parent, parent=menu) + edit_canvas_item_action.editFormCreated.connect(lambda w: self.editBuildingTemplateRequested.emit(w)) + canvas_item_menu.addAction(edit_canvas_item_action) + move_canvas_item_action = canvas_item_menu.addAction(f'Nutzungsschablone verschieben') + move_canvas_item_action.triggered.connect(lambda c: canvas_item.beginMove()) + + # identified annotation items (e.g SVG symbols) + if annotation_items: + for item_detail in annotation_items: # type: QgsRenderedAnnotationItemDetails + layer: QgsAnnotationLayer = QgsProject().instance().mapLayer(item_detail.layerId()) + item: QgsAnnotationItem = layer.item(item_detail.itemId()) + + symbol_path = ':/images/themes/default/mIconFieldText.svg' + + # TODO: dont run the following statement in CI, it sometimes causes python segfaults + # accessing item.format().background().type() seems to be problematic? maybe text annotation does not have background? + if not os.environ.get('CI'): + if isinstance(item, QgsAnnotationPointTextItem) and item.format().background().type() == QgsTextBackgroundSettings.ShapeSVG: + symbol_path = item.format().background().svgFile() + + xtype = layer.customProperties().value(f'xplanung/type') + annotation_item_menu = menu.addMenu(QIcon(symbol_path), xtype) + if isinstance(item, QgsAnnotationPointTextItem): + geom = QgsGeometry.fromPointXY(item.point()) + else: + geom = QgsGeometry(item.geometry()) + annotation_item_menu.menuAction().hovered.connect(lambda lyr=layer, g=geom: + self.menu_action_hovered(lyr, geom)) + + # get details from custom property, add common actions + move annatotation action + xplanung_id = layer.customProperties().value(f'xplanung/feat-{item_detail.itemId()}') + plan_xid = layer.customProperties().value(f'xplanung/plan-xid') + xplan_item = XPlanungItem(xid=xplanung_id, xtype=CLASSES[xtype], plan_xid=plan_xid) + + self.createCommonMenuEntries(annotation_item_menu, layer, item_detail.itemId()) + + move_annotation_item_action = MoveAnnotationItemAction(xplan_item, menu) + annotation_item_menu.addAction(move_annotation_item_action) + + # identified layers + for result in identify_results: + layer: QgsMapLayer = result.mLayer + feature: QgsFeature = result.mFeature + layer_menu = menu.addMenu(self.iconForWkbType(layer.wkbType()), layer.name()) + menu_action = layer_menu.menuAction() + menu_action.hovered.connect(lambda lyr=layer, feat=feature: self.menu_action_hovered(lyr, feat)) + if layer.customProperties().contains(f'xplanung/feat-{feature.id()}'): + # simple geometries don't have attributes to show + xtype = layer.customProperties().value(f'xplanung/type') + if xtype == 'XP_SimpleGeometry': + continue + + self.createCommonMenuEntries(layer_menu, layer, feature.id()) + + else: + layer_action = layer_menu.addAction('Als Planinhalt konfigurieren') + layer_action.triggered.connect(lambda checked, lyr=layer, feat=feature: + self.menu_action_triggered(ActionType.AddPlanContent, layer=lyr, + feature=feat)) + return menu + + def createCommonMenuEntries(self, menu: QMenu, layer: QgsMapLayer, feat_id: str, is_annotation=False): + xtype = layer.customProperties().value(f'xplanung/type') + xplanung_id = layer.customProperties().value(f'xplanung/feat-{feat_id}') + plan_xid = layer.customProperties().value(f'xplanung/plan-xid') + xplan_item = XPlanungItem(xid=xplanung_id, xtype=CLASSES[xtype], plan_xid=plan_xid) + + highlight_action = menu.addAction('Im Objektbaum markieren') + highlight_action.triggered.connect(lambda checked, xitem=xplan_item: + self.menu_action_triggered(ActionType.HighlightObjectTreeItem, + xplan_item=xitem)) + + data_action = menu.addAction('Sachdaten abfragen') + data_action.triggered.connect(lambda checked, xitem=xplan_item: + self.menu_action_triggered(ActionType.AccessAttributes, xplan_item=xitem)) + + if is_annotation: + return + + # if plan content is an area, add option to annotate with PO + if issubclass(xplan_item.xtype, PolygonGeometry) and issubclass(xplan_item.xtype, XP_Objekt): + annotate_action = menu.addAction('Präsentationsobjekt hinzufügen') + annotate_action.triggered.connect( + functools.partial(self.menu_action_triggered, ActionType.AddAnnotationItem, xplan_item)) + + def menu_action_hovered(self, layer: QgsMapLayer, feature: Union[QgsFeature, QgsGeometry]): + self.delete_highlight() + self.highlight = QgsHighlight(self.canvas, feature, layer) + self.highlight.setColor(QColor(255, 0, 0, 125)) + self.highlight.setBuffer(0.1) + self.highlight.show() + + def menu_action_triggered(self, action_type: ActionType, xplan_item: XPlanungItem = None, layer=None, feature=None): + if action_type == ActionType.AddPlanContent: + full_version_required_warning() + elif action_type == ActionType.AddAnnotationItem: + dialog = CreateAnnotateDialog(xplan_item) + dialog.annotationSaved.connect(self.featureSaved.emit) + dialog.show() + elif action_type == ActionType.AccessAttributes: + self.accessAttributesRequested.emit(xplan_item) + elif action_type == ActionType.HighlightObjectTreeItem: + self.highlightObjectTreeRequested.emit(xplan_item) + + def canvasReleaseEvent(self, event: QgsMapMouseEvent): + results = self.identify(event.x(), event.y(), QgsMapToolIdentify.TopDownAll, [], + QgsMapToolIdentify.VectorLayer, QgsIdentifyContext()) + global_pos = self.canvas.mapToGlobal(QPoint(event.x() + 5, event.y() + 5)) + canvas_pos = self.canvas.mapToScene(event.pos()) + + topmost_item = self.canvas.scene().itemAt(canvas_pos, QTransform()) + canvas_item = topmost_item if isinstance(topmost_item, BuildingTemplateItem) else None + + map_point = event.mapPoint() + search_rect = QgsRectangle(map_point.x(), map_point.y(), map_point.x(), map_point.y()) + search_rect.grow(self.searchRadiusMU(self.canvas)) + + renderedItemResults: QgsRenderedItemResults = self.canvas.renderedItemResults(False) + annotation_items = renderedItemResults.renderedAnnotationItemsInBounds(search_rect) + + self.menu(results, canvas_item=canvas_item, annotation_items=annotation_items).exec(global_pos) + + super(ContextMenuTool, self).canvasReleaseEvent(event) + + # from QGIS 3.20 use QgsIconUtils::iconForWkbType instead + # https://www.qgis.org/api/classQgsIconUtils.html#a58d939a4422eb18db911ccd2aeebaa44 + def iconForWkbType(self, wkb_type): + geometry_type = QgsWkbTypes.geometryType(wkb_type) + if geometry_type == QgsWkbTypes.PolygonGeometry: + return QIcon(':/images/themes/default/mIconPolygonLayer.svg') + if geometry_type == QgsWkbTypes.LineGeometry: + return QIcon(':/images/themes/default/mIconLineLayer.svg') + if geometry_type == QgsWkbTypes.PointGeometry: + return QIcon(':/images/themes/default/mIconPointLayer.svg') + + def delete_highlight(self): + if not self.highlight: + return + + self.highlight.hide() + self.canvas.scene().removeItem(self.highlight) + self.highlight = None diff --git a/src/SAGisXPlanung/Tools/IdentifyFeatureTool.py b/src/SAGisXPlanung/Tools/IdentifyFeatureTool.py new file mode 100644 index 0000000..b6056f7 --- /dev/null +++ b/src/SAGisXPlanung/Tools/IdentifyFeatureTool.py @@ -0,0 +1,65 @@ +import logging +import math +import os + +from qgis.core import QgsApplication +from qgis.gui import QgsMapToolIdentify +from qgis.core import QgsFeature, QgsMapLayer, Qgis +from qgis.utils import iface +from qgis.PyQt.QtCore import pyqtSignal, Qt +from qgis.PyQt.QtGui import QCursor, QIcon + +from SAGisXPlanung import BASE_DIR + +logger = logging.getLogger(__name__) + + +class IdentifyFeatureTool(QgsMapToolIdentify): + noFeatureSelected = pyqtSignal() + featureIdentified = pyqtSignal(QgsFeature) + + def __init__(self, canvas, layer=None, keep_selection=False): + self.canvas = canvas + self.iface = iface + self.layer = layer + self.keep_selection = keep_selection + super(IdentifyFeatureTool, self).__init__(self.canvas) + + cursor = QgsApplication.getThemeCursor(QgsApplication.CrossHair) + self.setCursor(cursor) + + def activate(self): + iface.mainWindow().statusBar().showMessage('Feature auf der Karte auswählen') + super(IdentifyFeatureTool, self).activate() + + def deactivate(self): + iface.mainWindow().statusBar().clearMessage() + super(IdentifyFeatureTool, self).deactivate() + + def setLayer(self, vl): + self.layer = vl + # super(IdentifyFeatureTool, self).setLayer(vl) + + def canvasReleaseEvent(self, event): + if event.button() == Qt.RightButton or not self.layer: + self.canvas.unsetMapTool(self) + return + + # event.snapPoint(QgsMapMouseEvent.SnapProjectConfig) + found_features = self.identify(event.x(), event.y(), [self.layer], QgsMapToolIdentify.LayerSelection) + + if not self.keep_selection: + layers = self.iface.mapCanvas().layers() + for l in layers: + if l.type() == QgsMapLayer.VectorLayer: + l.removeSelection() + + if len(found_features) > 0: + feature = found_features[0].mFeature + layer = found_features[0].mLayer + layer.select([feature.id()]) + self.featureIdentified.emit(feature) + else: + self.noFeatureSelected.emit() + + super(IdentifyFeatureTool, self).canvasReleaseEvent(event) diff --git a/src/SAGisXPlanung/Tools/__init__.py b/src/SAGisXPlanung/Tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/XPlan/XP_Praesentationsobjekte/__init__.py b/src/SAGisXPlanung/XPlan/XP_Praesentationsobjekte/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/XPlan/XP_Praesentationsobjekte/feature_types.py b/src/SAGisXPlanung/XPlan/XP_Praesentationsobjekte/feature_types.py new file mode 100644 index 0000000..fb5618f --- /dev/null +++ b/src/SAGisXPlanung/XPlan/XP_Praesentationsobjekte/feature_types.py @@ -0,0 +1,240 @@ +import os +from uuid import uuid4 + +from geoalchemy2 import Geometry, WKBElement, WKTElement +from geoalchemy2.shape import to_shape +from sqlalchemy import Column, ForeignKey, String, Integer, Boolean, ARRAY, Enum, Float, event, inspect +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from qgis.core import (QgsGeometry, QgsAnnotationPointTextItem, QgsAnnotationLayer, QgsTextFormat, QgsUnitTypes, + QgsTextBackgroundSettings, QgsPointXY, QgsCoordinateReferenceSystem, QgsProject) + +from SAGisXPlanung import Base, BASE_DIR +from SAGisXPlanung.BuildingTemplateItem import BuildingTemplateCellDataType +from SAGisXPlanung.GML.geometry import geometry_from_spatial_element +from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry +from SAGisXPlanung.XPlan.mixins import ElementOrderMixin, RelationshipMixin, MapCanvasMixin, PointGeometry +from SAGisXPlanung.XPlan.types import Angle + + +class XP_AbstraktesPraesentationsobjekt(RelationshipMixin, ElementOrderMixin, Base): + """ Abstrakte Basisklasse für alle Präsentationsobjekt + + Notiz: alle Präsentationsobjektklassen weichen von der vorgegebenen Vererbungshierarchie von XPlanung ab. + Die vorgegebenen Klassen sind in diesem XPlanung-Modul recht unpassend strukturiert. Viele Attribute werden + in den Basisklassen nicht verwendet oder sind einfach nicht benötigt. Außerdem sind die Vererbungshierarchien + zu tief angelegt, was die Struktur der Anwendung verkompliziert. Dadurch entstehen unnötig viele Tabellen in + der Datenbank. (bspw. durch 4-fache Vererbung: PO -> TPO -> PTO -> Nutzungschablone)""" + + __tablename__ = 'xp_po' + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + gehoertZuBereich_id = Column(UUID(as_uuid=True), ForeignKey('xp_bereich.id', ondelete='CASCADE')) + gehoertZuBereich = relationship('XP_Bereich', back_populates='praesentationsobjekt') + + dientZurDarstellungVon_id = Column(UUID(as_uuid=True), ForeignKey('xp_objekt.id', ondelete='CASCADE')) + dientZurDarstellungVon = relationship('XP_Objekt', back_populates='wirdDargestelltDurch') + + type = Column(String) + + __mapper_args__ = { + 'polymorphic_identity': 'xp_po', + 'polymorphic_on': type + } + __readonly_columns__ = ['position', 'skalierung', 'drehwinkel'] + + @classmethod + def avoid_export(cls): + return ['gehoertZuBereich', 'dientZurDarstellungVon'] + + def displayName(self): + return self.__class__.__name__ + + def geometry(self) -> QgsGeometry: + return geometry_from_spatial_element(self.position) + + def setGeometry(self, geom: QgsGeometry, srid: int = None): + if srid is None and self.position is None: + raise Exception('geometry needs a srid') + self.position = WKTElement(geom.asWkt(), srid=srid or self.position.srid) + + def srs(self): + return QgsCoordinateReferenceSystem(f'EPSG:{self.position.srid}') + + def remove_from_canvas(self): + """ Removes this annotation item from canvas if it is currently visible """ + layer: QgsAnnotationLayer = MapLayerRegistry().layerByFeature(str(self.id)) + if not layer: + return + + for item_id in layer.items().keys(): + id_prop = layer.customProperties().value(f'xplanung/feat-{item_id}') + if id_prop == str(self.id): + layer.removeItem(item_id) + break + + +@event.listens_for(XP_AbstraktesPraesentationsobjekt, 'after_delete', propagate=True) +def receive_after_delete(mapper, connection, target: XP_AbstraktesPraesentationsobjekt): + """ Removes annotation item from canvas after deletion """ + target.remove_from_canvas() + + +class XP_PPO(PointGeometry, MapCanvasMixin, XP_AbstraktesPraesentationsobjekt): + """ Punktförmiges Präsentationsobjekt """ + + __tablename__ = 'xp_ppo' + __mapper_args__ = { + 'polymorphic_identity': 'xp_ppo', + } + __default_size__ = 24 + + id = Column(ForeignKey("xp_po.id", ondelete='CASCADE'), primary_key=True) + + position = Column(Geometry(geometry_type='POINT')) + drehwinkel = Column(Angle, default=0) + skalierung = Column(Float, default=0.5) + + symbol_path = Column(String) + + def asFeature(self, fields=None): + shape = to_shape(self.position) + point = QgsPointXY(shape.x, shape.y) + item = QgsAnnotationPointTextItem('', point) + + label_format = QgsTextFormat() + label_format.setSizeUnit(QgsUnitTypes.RenderMapUnits) + label_format.setSize(self.__default_size__ * self.skalierung * 2.0) + + s = QgsTextBackgroundSettings() + s.setEnabled(True) + path = os.path.abspath(os.path.join(BASE_DIR, self.symbol_path)) + s.setSvgFile(path) + s.setType(QgsTextBackgroundSettings.ShapeSVG) + + label_format.setBackground(s) + item.setFormat(label_format) + item.setAngle(self.drehwinkel) + return item + + @classmethod + def asLayer(cls, srid, plan_xid, name=None, geom_type=None): + layer = QgsAnnotationLayer('Symbole', QgsAnnotationLayer.LayerOptions(QgsProject.instance().transformContext())) + layer.setCrs(QgsCoordinateReferenceSystem(f'EPSG:{srid}')) + layer.setCustomProperty("skipMemoryLayersCheck", 1) + layer.setCustomProperty('xplanung/type', cls.__name__) + layer.setCustomProperty('xplanung/plan-xid', str(plan_xid)) + # TODO: properties are not persistent, see: https://github.com/qgis/QGIS/issues/47463 + return layer + + @classmethod + def avoid_export(cls): + return super(XP_PPO, cls).avoid_export() + ['symbol_path'] + + @classmethod + def hidden_inputs(cls): + return ['symbol_path'] + + +@event.listens_for(XP_PPO, 'after_update') +def update_map_display(mapper, connection, xp_ppo: XP_PPO): + """Update map symbol if attribute 'symbol_path' changes """ + # detect if symbol path changed + history = inspect(xp_ppo).attrs.symbol_path.history + if not history.has_changes(): + return + + layer: QgsAnnotationLayer = MapLayerRegistry().layerByFeature(str(xp_ppo.id)) + if not layer: + return + + for item_id, item in layer.items().items(): + id_prop = layer.customProperties().value(f'xplanung/feat-{item_id}') + if id_prop == str(xp_ppo.id): + background_setting = item.format().background() + path = os.path.abspath(os.path.join(BASE_DIR, xp_ppo.symbol_path)) + background_setting.setSvgFile(path) + + item.format().setBackground(background_setting) + + layer.triggerRepaint() + break + + +class XP_PTO(PointGeometry, MapCanvasMixin, XP_AbstraktesPraesentationsobjekt): + """ Textförmiges Präsentationsobjekt mit punktförmiger Festlegung der Textposition """ + + __tablename__ = 'xp_pto' + __mapper_args__ = { + 'polymorphic_identity': 'xp_pto', + } + __default_size__ = 6 + + id = Column(ForeignKey("xp_po.id", ondelete='CASCADE'), primary_key=True) + + schriftinhalt = Column(String) + + skalierung = Column(Float, default=0.5) + position = Column(Geometry(geometry_type='POINT')) + drehwinkel = Column(Angle, default=0) + + def asFeature(self, fields=None): + shape = to_shape(self.position) + point = QgsPointXY(shape.x, shape.y) + item = QgsAnnotationPointTextItem(self.schriftinhalt, point) + + label_format = QgsTextFormat() + label_format.setSizeUnit(QgsUnitTypes.RenderMapUnits) + label_format.setSize(self.__default_size__ * self.skalierung * 2.0) + + item.setFormat(label_format) + item.setAngle(self.drehwinkel) + return item + + @classmethod + def asLayer(cls, srid, plan_xid, name=None, geom_type=None): + layer = QgsAnnotationLayer('Text', QgsAnnotationLayer.LayerOptions(QgsProject.instance().transformContext())) + layer.setCrs(QgsCoordinateReferenceSystem(f'EPSG:{srid}')) + layer.setCustomProperty("skipMemoryLayersCheck", 1) + layer.setCustomProperty('xplanung/type', cls.__name__) + layer.setCustomProperty('xplanung/plan-xid', str(plan_xid)) + # TODO: properties are not persistent, see: https://github.com/qgis/QGIS/issues/47463 + return layer + + +class XP_Nutzungsschablone(PointGeometry, XP_AbstraktesPraesentationsobjekt): + """ Modelliert eine Nutzungsschablone. Die darzustellenden Attributwerte werden zeilenweise in die + Nutzungsschablone geschrieben """ + + def __init__(self): + super(XP_Nutzungsschablone, self).__init__() + + self.hidden = True + self.data_attributes = BuildingTemplateCellDataType.as_default() + + def set_defaults(self, row_count=3): + self.data_attributes = BuildingTemplateCellDataType.as_default(row_count) + + __tablename__ = 'xp_nutzungsschablone' + __mapper_args__ = { + 'polymorphic_identity': 'xp_nutzungsschablone', + } + + id = Column(ForeignKey("xp_po.id", ondelete='CASCADE'), primary_key=True) + + skalierung = Column(Float, default=0.5) + position = Column(Geometry(geometry_type='POINT')) + drehwinkel = Column(Angle, default=0) + + spaltenAnz = Column(Integer, nullable=False, default=2) + zeilenAnz = Column(Integer, nullable=False, default=3) + + # non xplanung attributes + hidden = Column(Boolean) + data_attributes = Column(ARRAY(Enum(BuildingTemplateCellDataType))) + + @classmethod + def avoid_export(cls): + return super(XP_Nutzungsschablone, cls).avoid_export() + ['hidden', 'data_attributes'] diff --git a/src/SAGisXPlanung/XPlan/__init__.py b/src/SAGisXPlanung/XPlan/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/XPlan/codelists.py b/src/SAGisXPlanung/XPlan/codelists.py new file mode 100644 index 0000000..399b021 --- /dev/null +++ b/src/SAGisXPlanung/XPlan/codelists.py @@ -0,0 +1,32 @@ + +class CodeList: + """ XP_Basisobjekte::CodeList """ + + XP_MIME_TYPES = [ + "application/pdf", + "application/zip", + "application/xml", + "application/msword", + "application/msexcel", + "application/vnd.ogc.sld+xml", + "application/vnd.ogc.wms_xml", + "application/vnd.ogc.gml", + "application/vnd.shp", + "application/vnd.dbf", + "application/vnd.shx", + "application/octet-stream", + "image/vnd.dxf", + "image/vnd.dwg", + "image/jpg", + "image/png", + "image/tiff", + "image/bmp", + "image/ecw", + "image/svg+xml", + "text/html", + "text/plain" + ] + + XP_GesetzlicheGrundlage = [ + "" + ] diff --git a/src/SAGisXPlanung/XPlan/conversions.py b/src/SAGisXPlanung/XPlan/conversions.py new file mode 100644 index 0000000..0ce1697 --- /dev/null +++ b/src/SAGisXPlanung/XPlan/conversions.py @@ -0,0 +1,103 @@ +import logging + +from sqlalchemy import types + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.XPlan.enums import XP_Rechtscharakter +from SAGisXPlanung.config import export_version + +logger = logging.getLogger(__name__) + + +class BP_Rechtscharakter_EnumType(types.TypeDecorator): + impl = types.Enum + + def process_bind_param(self, value, dialect): + if value == "FestsetzungBPlan" or (isinstance(value, XP_Rechtscharakter) and value == XP_Rechtscharakter.FestsetzungBPlan): + return "Festsetzung" + if isinstance(value, XP_Rechtscharakter): + return value.name + return value + + process_literal_param = process_bind_param + + def process_result_value(self, value, dialect): + return value + + +class FP_Rechtscharakter_EnumType(types.TypeDecorator): + impl = types.Enum + + def process_bind_param(self, value, dialect): + if value == "DarstellungFPlan" or (isinstance(value, XP_Rechtscharakter) and value == XP_Rechtscharakter.DarstellungFPlan): + return "Darstellung" + if isinstance(value, XP_Rechtscharakter): + return value.name + return value + + process_literal_param = process_bind_param + + def process_result_value(self, value, dialect): + return value + + +class SO_Rechtscharakter_EnumType(types.TypeDecorator): + impl = types.Enum + + def process_bind_param(self, value, dialect): + if value == "FestsetzungImLP" or (isinstance(value, XP_Rechtscharakter) and value == XP_Rechtscharakter.FestsetzungImLP): + return "InhaltLPlan" + if isinstance(value, XP_Rechtscharakter): + return value.name + return value + + process_literal_param = process_bind_param + + def process_result_value(self, value, dialect): + return value + + +class XP_Rechtscharakter_EnumType(types.TypeDecorator): + impl = types.Enum + + def process_bind_param(self, value, dialect): + from SAGisXPlanung.BPlan.BP_Basisobjekte.enums import BP_Rechtscharakter + from SAGisXPlanung.FPlan.FP_Basisobjekte.enums import FP_Rechtscharakter + from SAGisXPlanung.SonstigePlanwerke.SO_Basisobjekte import SO_Rechtscharakter + + if isinstance(value, (SO_Rechtscharakter, BP_Rechtscharakter, FP_Rechtscharakter)): + return value.to_xp_rechtscharakter() + + if export_version() == XPlanVersion.FIVE_THREE: + for cls in [SO_Rechtscharakter, BP_Rechtscharakter, FP_Rechtscharakter]: + try: + char = cls[value] + return char.to_xp_rechtscharakter() + except Exception as e: + pass + return value + + process_literal_param = process_bind_param + + def process_result_value(self, value, dialect): + if export_version() == XPlanVersion.FIVE_THREE: + + from SAGisXPlanung.BPlan.BP_Basisobjekte.enums import BP_Rechtscharakter + from SAGisXPlanung.FPlan.FP_Basisobjekte.enums import FP_Rechtscharakter + from SAGisXPlanung.SonstigePlanwerke.SO_Basisobjekte import SO_Rechtscharakter + + if value == XP_Rechtscharakter.FestsetzungBPlan: + return BP_Rechtscharakter.Festsetzung + if value == XP_Rechtscharakter.DarstellungFPlan: + return FP_Rechtscharakter.Darstellung + if value == XP_Rechtscharakter.FestsetzungImLP: + return SO_Rechtscharakter.InhaltLPlan + + for cls in [SO_Rechtscharakter, BP_Rechtscharakter, FP_Rechtscharakter]: + try: + char = cls[value.name] + return char + except Exception as e: + pass + + return value diff --git a/src/SAGisXPlanung/XPlan/core.py b/src/SAGisXPlanung/XPlan/core.py new file mode 100644 index 0000000..f2239c6 --- /dev/null +++ b/src/SAGisXPlanung/XPlan/core.py @@ -0,0 +1,31 @@ +import functools +from dataclasses import dataclass +from typing import List + +from sqlalchemy import Column + +from SAGisXPlanung import XPlanVersion + + +class XPCol(Column): + + def __init__(self, *args, version=XPlanVersion.FIVE_THREE, attribute=None, import_attr=None, **kwargs): + self.version = version + self.attribute = attribute + self.import_attr = import_attr + super(XPCol, self).__init__(*args, **kwargs) + + +@dataclass +class XPRelationshipProperty: + rel_name: str + xplan_attribute: str + allowed_version: XPlanVersion + + +def xp_version(versions: List[XPlanVersion]): + """ Class-Decorator to denote the XPlan-Version a class belongs to.""" + def decorator_versions(cls): + setattr(cls, 'xp_versions', versions) + return cls + return decorator_versions diff --git a/src/SAGisXPlanung/XPlan/data_types.py b/src/SAGisXPlanung/XPlan/data_types.py new file mode 100644 index 0000000..8e64025 --- /dev/null +++ b/src/SAGisXPlanung/XPlan/data_types.py @@ -0,0 +1,306 @@ +import datetime +from uuid import uuid4 + +from lxml import etree +from sqlalchemy import Column, String, Enum, Date, ForeignKey, CheckConstraint, Boolean, Table, event +from sqlalchemy.dialects.postgresql import UUID, BYTEA +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import relationship, deferred + +from SAGisXPlanung import Base, XPlanVersion +from SAGisXPlanung.XPlan.codelists import CodeList +from SAGisXPlanung.XPlan.core import XPCol +from SAGisXPlanung.XPlan.enums import XP_ExterneReferenzArt, XP_ExterneReferenzTyp, XP_SPEMassnahmenTypen, \ + XP_ArtHoehenbezug, XP_ArtHoehenbezugspunkt +from SAGisXPlanung.XPlan.mixins import RelationshipMixin, ElementOrderMixin, ElementOrderDeclarativeInheritanceFixMixin +from SAGisXPlanung.XPlan.types import RefURL, RegExString, ConformityException, LargeString, Length + +XP_PlanXP_GemeindeAssoc = Table('xp_plan_gemeinde', Base.metadata, + Column('bp_plan_id', UUID(as_uuid=True), ForeignKey('bp_plan.id', ondelete='CASCADE')), + Column('fp_plan_id', UUID(as_uuid=True), ForeignKey('fp_plan.id', ondelete='CASCADE')), + XPCol('lp_plan_id', UUID(as_uuid=True), ForeignKey('lp_plan.id', ondelete='CASCADE'), version=XPlanVersion.SIX), + Column('gemeinde_id', UUID(as_uuid=True), ForeignKey('xp_gemeinde.id')) +) + + +class XP_Gemeinde(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation einer für die Aufstellung des Plans zuständigen Gemeinde. """ + + __tablename__ = 'xp_gemeinde' + __accessColumn__ = 'gemeindeName' # column to access by name/string representation + __avoidRelation__ = ['bp_plans', 'fp_plans'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + bp_plans = relationship("BP_Plan", back_populates="gemeinde", secondary=XP_PlanXP_GemeindeAssoc) + fp_plans = relationship("FP_Plan", back_populates="gemeinde", secondary=XP_PlanXP_GemeindeAssoc) + lp_plans = relationship("LP_Plan", back_populates="gemeinde", secondary=XP_PlanXP_GemeindeAssoc) + + ags = Column(RegExString(r'\d{8}\b', 8, error_msg='Gemeindeschlüssel muss aus 8 Ziffern bestehen'), + doc='Gemeindeschlüssel (AGS)') + rs = Column(RegExString(r'\d{12}\b', 12, error_msg='Regionalschlüssel muss aus 12 Ziffern bestehen'), + doc='Regionalschlüssel') + gemeindeName = Column(String(), nullable=False, doc='Name der Gemeinde') + ortsteilName = Column(String(), doc='Ortsteilbezeichnung') + + def __str__(self): + return self.gemeindeName + f"{f', OT {self.ortsteilName}' if self.ortsteilName else ''}" + + def __eq__(self, other): + if type(other) is type(self): + return self.ags == other.ags and self.ortsteilName == other.ortsteilName + return False + + def validate(self): + if not self.ags and not self.rs: + raise ConformityException('Die Attribute ags und rs dürfen nicht beide unbelegt sein', + '3.2.5.1', self.__class__.__name__) + + +@event.listens_for(XP_Gemeinde, 'before_insert') +@event.listens_for(XP_Gemeinde, 'before_update') +def checkIntegrityXP_Gemeinde(mapper, connection, xp_gemeinde): + if not xp_gemeinde.ags and not xp_gemeinde.rs: + raise IntegrityError("Die Attribute 'ags' und 'rs' dürfen nicht beide unbelegt sein", + { + 'name': "Konsistenz der Attribute ags (Amtlicher Gemeindeschlüssel) und rs (Regionalschlüssel)", + 'nummer': '3.2.5.1' + }, + xp_gemeinde) + + +class XP_ExterneReferenz(RelationshipMixin, ElementOrderMixin, Base): + """ XP_ExterneReferenz + + Verweis auf ein extern gespeichertes Dokument oder einen extern gespeicherten, georeferenzierten Plan. + Einer der beiden Attribute "referenzName" bzw. "referenzURL" muss belegt sein. + """ + + __tablename__ = 'xp_externe_referenz' + __table_args__ = ( + CheckConstraint('NOT("referenzName" IS NULL AND "referenzURL" IS NULL)'), + ) + __avoidRelation__ = ['bereich', 'baugebiet', 'bp_schutzflaeche_massnahme', 'bp_schutzflaeche_plan', + 'veraenderungssperre'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + georefURL = Column(String, doc='Verweis auf Georeferenzierungs-Datei') + art = Column(Enum(XP_ExterneReferenzArt), doc='Art der Referenz') + referenzName = Column(String, nullable=False, doc='Name bzw. Titel') + referenzURL = Column(RefURL, nullable=False, doc='URI der Referenz') + referenzMimeType = Column(Enum(*CodeList.XP_MIME_TYPES, name="xp_mime_types"), doc='Dateityp') + beschreibung = Column(LargeString, doc='Beschreibung') + datum = Column(Date, doc='Datum') + + file = deferred(Column(BYTEA)) + + bereich_id = Column(UUID(as_uuid=True), ForeignKey('xp_bereich.id', ondelete='CASCADE')) + bereich = relationship("XP_Bereich", back_populates="refScan") + + baugebiet_id = Column(UUID(as_uuid=True), ForeignKey('bp_baugebiet.id', ondelete='CASCADE')) + baugebiet = relationship("BP_BaugebietsTeilFlaeche", back_populates="refGebaeudequerschnitt") + + bp_schutzflaeche_massnahme_id = Column(UUID(as_uuid=True), ForeignKey('bp_schutzflaeche.id', ondelete='CASCADE')) + bp_schutzflaeche_massnahme = relationship("BP_SchutzPflegeEntwicklungsFlaeche", + foreign_keys=[bp_schutzflaeche_massnahme_id], + back_populates="refMassnahmenText") + + bp_schutzflaeche_plan_id = Column(UUID(as_uuid=True), ForeignKey('bp_schutzflaeche.id', ondelete='CASCADE')) + bp_schutzflaeche_plan = relationship("BP_SchutzPflegeEntwicklungsFlaeche", foreign_keys=[bp_schutzflaeche_plan_id], + back_populates="refLandschaftsplan") + + veraenderungssperre_id = Column(UUID(as_uuid=True), ForeignKey('bp_veraenderungssperre_daten.id', ondelete='CASCADE')) + veraenderungssperre = relationship("BP_VeraenderungssperreDaten", back_populates="refBeschluss") + + type = Column(String(50)) + + __mapper_args__ = { + 'polymorphic_identity': 'xp_externe_referenz', + 'polymorphic_on': type + } + + def validate(self): + if not self.referenzName and not self.referenzURL: + raise ConformityException('Mindestens eines der Attribute referenzName und ' + 'referenzURL muss belegt sein.', + '3.2.4.2', self.__class__.__name__) + + @classmethod + def hidden_inputs(cls): + return ['file'] + + @classmethod + def avoid_export(cls): + return ['file', 'baugebiet_id', 'bereich_id', 'bereich', 'bp_schutzflaeche_massnahme_id', + 'bp_schutzflaeche_plan_id', 'veraenderungssperre_id'] + + +class XP_SpezExterneReferenz(XP_ExterneReferenz): + """ Ergänzung des Datentyps XP_ExterneReferenz um ein Attribut zur semantischen Beschreibung des referierten Dokuments. """ + + __tablename__ = 'xp_spez_externe_referenz' + __mapper_args__ = { + 'polymorphic_identity': 'xp_spez_externe_referenz', + } + + id = Column(ForeignKey("xp_externe_referenz.id", ondelete='CASCADE'), primary_key=True) + + typ = Column(Enum(XP_ExterneReferenzTyp), doc='Typ/Inhalt der Referenz') + plan_id = Column(UUID(as_uuid=True), ForeignKey('xp_plan.id', ondelete='CASCADE')) + plan = relationship("XP_Plan", back_populates="externeReferenz") + + @classmethod + def avoid_export(cls): + base = super(XP_SpezExterneReferenz, cls).avoid_export() + return base + ['plan_id'] + + +class XP_Plangeber(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Institution, die für den Plan verantwortlich ist. """ + + __tablename__ = 'xp_plangeber' + __accessColumn__ = 'name' + __avoidRelation__ = ['bp_plans', 'fp_plans', 'lp_plans'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + bp_plans = relationship("BP_Plan", back_populates="plangeber") + fp_plans = relationship("FP_Plan", back_populates="plangeber") + lp_plans = relationship("LP_Plan", back_populates="plangeber") + + name = Column(String, nullable=False, doc='Name') + kennziffer = Column(String, doc='Kennziffer') + + def __str__(self): + return self.name + + def __eq__(self, other): + if type(other) is type(self): + return self.name == other.name + return False + + +@event.listens_for(XP_Plangeber, 'before_insert') +@event.listens_for(XP_Plangeber, 'before_update') +def checkIntegrityXP_Plangeber(mapper, connection, xp_plangeber): + if not xp_plangeber.name: + raise IntegrityError("Das Attribut 'name' benötigt einen Wert", None, xp_plangeber) + + +class XP_VerfahrensMerkmal(RelationshipMixin, ElementOrderMixin, Base): + """ Vermerk eines am Planungsverfahrens beteiligten Akteurs. """ + + __tablename__ = "xp_verfahrens_merkmal" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + plan_id = Column(UUID(as_uuid=True), ForeignKey('xp_plan.id', ondelete='CASCADE')) + plan = relationship("XP_Plan", back_populates="verfahrensMerkmale") + + vermerk = Column(LargeString, nullable=False) + datum = Column(Date, nullable=False) + signatur = Column(String, nullable=False) + signiert = Column(Boolean, nullable=False) + + @classmethod + def avoid_export(cls): + return ['plan_id'] + + +class XP_SPEMassnahmenDaten(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation der Attribute für einer Schutz-, Pflege- oder Entwicklungsmaßnahme """ + + __tablename__ = 'xp_spe_daten' + __avoidRelation__ = ['bp_schutzflaeche'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + klassifizMassnahme = Column(Enum(XP_SPEMassnahmenTypen)) + massnahmeText = Column(String) + massnahmeKuerzel = Column(String) + + bp_schutzflaeche_id = Column(UUID(as_uuid=True), ForeignKey('bp_schutzflaeche.id', ondelete='CASCADE')) + bp_schutzflaeche = relationship('BP_SchutzPflegeEntwicklungsFlaeche', back_populates='massnahme') + + @classmethod + def avoid_export(cls): + return ['bp_schutzflaeche_id'] + + +class XP_GesetzlicheGrundlage(RelationshipMixin, ElementOrderMixin, Base): + """ Angeben zur Spezifikation der gesetzlichen Grundlage eines Planinhalts. """ + + __tablename__ = 'xp_gesetzliche_grundlage' + __avoidRelation__ = ['xp_objekts', + 'fp_bau_nvo', 'fp_bau_gb', 'fp_bau_sonst', + 'bp_bau_nvo', 'bp_bau_gb', 'bp_bau_sonst'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + xp_objekts = relationship("XP_Objekt", back_populates="gesetzlicheGrundlage") + + fp_bau_nvo = relationship("FP_Plan", back_populates="versionBauNVO", foreign_keys='FP_Plan.versionBauNVO_id') + fp_bau_gb = relationship("FP_Plan", back_populates="versionBauGB", foreign_keys='FP_Plan.versionBauGB_id') + fp_bau_sonst = relationship("FP_Plan", back_populates="versionSonstRechtsgrundlage", + foreign_keys='FP_Plan.versionSonstRechtsgrundlage_id') + + bp_bau_nvo = relationship("BP_Plan", back_populates="versionBauNVO", foreign_keys='BP_Plan.versionBauNVO_id') + bp_bau_gb = relationship("BP_Plan", back_populates="versionBauGB", foreign_keys='BP_Plan.versionBauGB_id') + bp_bau_sonst = relationship("BP_Plan", back_populates="versionSonstRechtsgrundlage", + foreign_keys='BP_Plan.versionSonstRechtsgrundlage_id') + + name = Column(String) + datum = Column(Date) + detail = Column(String) + + def __str__(self): + return f'{self.name}, {self.datum}' + + def __eq__(self, other): + if type(other) is type(self): + return self.name == other.name and str(self.datum) == str(other.datum) + return False + + +class XP_Hoehenangabe(RelationshipMixin, ElementOrderMixin, Base): + """ Spezifikation einer Angabe zur vertikalen Höhe oder zu einem Bereich vertikaler Höhen. Es ist möglich, + spezifische Höhenangaben (z.B. die First- oder Traufhöhe eines Gebäudes) vorzugeben oder einzuschränken, + oder den Gültigkeitsbereich eines Planinhalts auf eine bestimmte Höhe (hZwingend) bzw. einen Höhenbereich + (hMin - hMax) zu beschränken, was vor allem bei der höhenabhängigen Festsetzung einer überbaubaren + Grundstücksfläche (BP_UeberbaubareGrundstuecksflaeche), einer Baulinie (BP_Baulinie) oder einer Baugrenze + (BP_Baugrenze) relevant ist. In diesem Fall bleiben die Attribute bezugspunkt und abweichenderBezugspunkt + unbelegt """ + + __tablename__ = 'xp_hoehenangabe' + __avoidRelation__ = ['xp_objekt', 'dachgestaltung'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + abweichenderHoehenbezug = Column(String) + hoehenbezug = Column(Enum(XP_ArtHoehenbezug)) + abweichenderBezugspunkt = Column(String) + bezugspunkt = Column(Enum(XP_ArtHoehenbezugspunkt)) + hMin = Column(Length) + hMax = Column(Length) + hZwingend = Column(Length) + h = Column(Length) + + xp_objekt_id = Column(UUID(as_uuid=True), ForeignKey('xp_objekt.id', ondelete='CASCADE')) + xp_objekt = relationship("XP_Objekt", back_populates="hoehenangabe") + + dachgestaltung_id = XPCol(UUID(as_uuid=True), ForeignKey('bp_dachgestaltung.id', ondelete='CASCADE'), + version=XPlanVersion.SIX) + dachgestaltung = relationship("BP_Dachgestaltung", back_populates="hoehenangabe") + + def to_xplan_node(self, node=None, version=XPlanVersion.FIVE_THREE): + from SAGisXPlanung.GML.GMLWriter import GMLWriter + + this_node = etree.SubElement(node, f"{{{node.nsmap['xplan']}}}{self.__class__.__name__}") + GMLWriter.write_attributes(this_node, self, version) + return node + + @classmethod + def avoid_export(cls): + return ['xp_objekt_id', 'dachgestaltung_id'] + + @classmethod + def hidden_inputs(cls): + return ['dachgestaltung'] diff --git a/src/SAGisXPlanung/XPlan/enums.py b/src/SAGisXPlanung/XPlan/enums.py new file mode 100644 index 0000000..bbeb0db --- /dev/null +++ b/src/SAGisXPlanung/XPlan/enums.py @@ -0,0 +1,635 @@ +import logging +from enum import Enum + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.FPlan.FP_Basisobjekte.enums import FP_Rechtscharakter +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin + +logger = logging.getLogger(__name__) + + +class XP_VerlaengerungVeraenderungssperre(XPlanungEnumMixin, Enum): + """ Gibt an, ob die Veränderungssperre bereits ein-oder zweimal verlängert wurde. """ + + Keine = 1000 + ErsteVerlaengerung = 2000 + ZweiteVerlaengerung = 3000 + + +class XP_BedeutungenBereich(XPlanungEnumMixin, Enum): + """ Spezifikation der semantischen Bedeutung eines Bereiches. """ + + Teilbereich = 1600 + Kompensationsbereich = 1800 + Sonstiges = 9999 + + +class XP_ExterneReferenzArt(XPlanungEnumMixin, Enum): + """ Typisierung der referierten Dokumente: Beliebiges Dokument oder georeferenzierter Plan. """ + + Dokument = "Dokument" + PlanMitGeoreferenz = "PlanMitGeoreferenz" + + +class XP_ExterneReferenzTyp(XPlanungEnumMixin, Enum): + """ Typ / Inhalt eines referierten Dokuments oder Rasterplans. """ + + def __new__(cls, *args, **kwds): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: int, version: XPlanVersion = None): + self._xplan_version = version + + @property + def version(self) -> XPlanVersion: + return self._xplan_version + + Beschreibung = 1000 + Begruendung = 1010 + Legende = 1020 + Rechtsplan = 1030 + Plangrundlage = 1040 + Umweltbericht = 1050 + Satzung = 1060 + Verordnung = 1065 + Karte = 1070 + Erlaeuterung = 1080 + ZusammenfassendeErklaerung = 1090 + Koordinatenliste = 2000 + Grundstuecksverzeichnis = 2100 + Pflanzliste = 2200 + Gruenordnungsplan = 2300 + Erschliessungsvertrag = 2400 + Durchfuehrungsvertrag = 2500 + StaedtebaulicherVertrag = 2600 + UmweltbezogeneStellungnahmen = 2700 + Beschluss = 2800 + VorhabenUndErschliessungsplan = 2900 + MetadatenPlan = 3000 + Genehmigung = 4000 + Bekanntmachung = 5000 + Schutzgebietsverordnung = 6000, XPlanVersion.SIX + Rechtsverbindlich = 9998 + Informell = 9999 + + +class XP_Rechtsstand(XPlanungEnumMixin, Enum): + """ Angabe, ob der Planinhalt bereits besteht, geplant ist, oder zukünftig wegfallen soll. """ + + Geplant = 1000 + Bestehend = 2000 + Fortfallend = 3000 + + +class XP_AllgArtDerBaulNutzung(XPlanungEnumMixin, Enum): + """ Spezifikation der allgemeinen Art der baulichen Nutzung """ + + WohnBauflaeche = 1000 + GemischteBauflaeche = 2000 + GewerblicheBauflaeche = 3000 + SonderBauflaeche = 4000 + Sonstiges = 9999 + + +class XP_BesondereArtDerBaulNutzung(XPlanungEnumMixin, Enum): + """ Festsetzung der Art der baulichen Nutzung (§9, Abs. 1, Nr. 1 BauGB) """ + + def __new__(cls, *args, **kwds): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: int, version: XPlanVersion = None): + self._xplan_version = version + + @property + def version(self) -> XPlanVersion: + return self._xplan_version + + Kleinsiedlungsgebiet = 1000 + ReinesWohngebiet = 1100 + AllgWohngebiet = 1200 + BesonderesWohngebiet = 1300 + Dorfgebiet = 1400 + DoerflichesWohngebiet = 1450 + Mischgebiet = 1500 + UrbanesGebiet = 1550 + Kerngebiet = 1600 + Gewerbegebiet = 1700 + Industriegebiet = 1800 + SondergebietErholung = 2000 + SondergebietSonst = 2100 + Wochenendhausgebiet = 3000 + Sondergebiet = 4000 + SonstigesGebiet = 9999 + + +class XP_Sondernutzungen(XPlanungEnumMixin, Enum): + """ Differenziert Sondernutzungen nach §10 und §11 der BauNVO von 1977 und 1990. """ + + def __new__(cls, *args, **kwds): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: int, version: XPlanVersion = None): + self._xplan_version = version + + @property + def version(self) -> XPlanVersion: + return self._xplan_version + + Wochendhausgebiet = 1000 + Ferienhausgebiet = 1100 + Campingplatzgebiet = 1200 + Kurgebiet = 1300 + SonstSondergebietErholung = 1400 + Einzelhandelsgebiet = 1500 + GrossflaechigerEinzelhandel = 1600 + Ladengebiet = 16000 + Einkaufszentrum = 16001 + SonstGrossflEinzelhandel = 16002 + SondergebietGrosshandel = 1650, XPlanVersion.SIX + Verkehrsuebungsplatz = 1700 + Hafengebiet = 1800 + SondergebietErneuerbareEnergie = 1900 + SondergebietMilitaer = 2000 + SondergebietLandwirtschaft = 2100 + SondergebietSport = 2200 + SondergebietGesundheitSoziales = 2300 + Klinikgebiet = 23000 + Golfplatz = 2400 + SondergebietKultur = 2500 + SondergebietTourismus = 2600 + SondergebietBueroUndVerwaltung = 2700 + SondergebietJustiz = 2720 + SondergebietHochschuleForschung = 2800 + SondergebietMesse = 2900 + SondergebietAndereNutzungen = 9999 + + +class XP_AbweichungBauNVOTypen(XPlanungEnumMixin, Enum): + """ Art der zulässigen Abweichung von der BauNVO """ + + KeineAbweichung = None + EinschraenkungNutzung = 1000 + AusschlussNutzung = 2000 + AusweitungNutzung = 3000 + SonstAbweichung = 9999 + + +class XP_Nutzungsform(XPlanungEnumMixin, Enum): + """ Art der Nutzungsform """ + + Privat = 1000 + Oeffentlich = 2000 + + +class XP_ZweckbestimmungLandwirtschaft(XPlanungEnumMixin, Enum): + """ Zweckbestimmungen für landwirtschaftliche Flächen """ + + LandwirtschaftAllgemein = 1000 + Ackerbau = 1100 + WiesenWeidewirtschaft = 1200 + GartenbaulicheErzeugung = 1300 + Obstbau = 1400 + Weinbau = 1500 + Imkerei = 1600 + Binnenfischerei = 1700 + Sonstiges = 9999 + + +class XP_ZweckbestimmungWald(XPlanungEnumMixin, Enum): + """ Zweckbestimmungen für Waldflächen """ + + Naturwald = 1000 + Waldschutzgebiet = 10000 + Nutzwald = 1200 + Erholungswald = 1400 + Schutzwald = 1600 + Bodenschutzwald = 16000 + Biotopschutzwald = 16001 + NaturnaherWald = 16002 + SchutzwaldSchaedlicheUmwelteinwirkungen = 16003 + Schonwald = 16004 + Bannwald = 1700 + FlaecheForstwirtschaft = 1800 + ImmissionsgeschaedigterWald = 1900 + Sonstiges = 9999 + + +class XP_EigentumsartWald(XPlanungEnumMixin, Enum): + """ Festlegung der Eigentumsart eines Waldes""" + + OeffentlicherWald = 1000 + Staatswald = 1100 + Koerperschaftswald = 1200 + Kommunalwald = 12000 + Stiftungswald = 12001 + Privatwald = 2000 + Gemeinschaftswald = 20000 + Genossenschaftswald = 20001 + Kirchenwald = 3000 + Sonstiges = 9999 + + +class XP_WaldbetretungTyp(XPlanungEnumMixin, Enum): # TODO: this should probably be a checkable combobox + """ Festlegung zusätzlicher, normalerweise nicht-gestatteter Aktivitäten, die in dem + Wald ausgeführt werden dürfen, nach §14 Abs. 2 Bundeswaldgesetz """ + + KeineZusaetzlicheBetretung = None + Radfahren = 1000 + Reiten = 2000 + Fahren = 3000 + Hundesport = 4000 + Sonstiges = 9999 + + +class XP_ZweckbestimmungGewaesser(XPlanungEnumMixin, Enum): + """ Zweckbestimmungen für Wasserflächen""" + + Hafen = 1000 + Sportboothafen = 10000 + Wasserflaeche = 1100 + Fliessgewaesser = 1200 + Sonstiges = 9999 + + +class XP_ZweckbestimmungVerEntsorgung(XPlanungEnumMixin, Enum): + """ Zweckbestimmungen für Ver-/Entsorgungsflächen""" + + def __new__(cls, *args, **kwds): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: int, version: XPlanVersion = None): + self._xplan_version = version + + @property + def version(self) -> XPlanVersion: + return self._xplan_version + + Elektrizitaet = 1000 + Hochspannungsleitung = 10000 + TrafostationUmspannwerk = 10001 + Solarkraftwerk = 10002 + Windkraftwerk = 10003 + Geothermiekraftwerk = 10004 + Elektrizitaetswerk = 10005 + Wasserkraftwerk = 10006 + BiomasseKraftwerk = 10007 + Kabelleitung = 10008 + Niederspannungsleitung = 10009 + Leitungsmast = 10010 + Kernkraftwerk = 10011 + Kohlekraftwerk = 10012 + Gaskraftwerk = 10013 + Gas = 1200 + Ferngasleitung = 12000 + Gaswerk = 12001 + Gasbehaelter = 12002 + Gasdruckregler = 12003 + Gasstation = 12004 + Gasleitung = 12005 + Erdoel = 1300 + Erdoelleitung = 13000 + Bohrstelle = 13001 + Erdoelpumpstation = 13002 + Oeltank = 13003 + Waermeversorgung = 1400 + Blockheizkraftwerk = 14000 + Fernwaermeleitung = 14001 + Fernheizwerk = 14002 + Wasser = 1600 + Wasserwerk = 16000 + Wasserleitung = 16001 + Wasserspeicher = 16002 + Brunnen = 16003 + Pumpwerk = 16004 + Quelle = 16005 + Abwasser = 1800 + Abwasserleitung = 18000 + Abwasserrueckhaltebecken = 18001 + Abwasserpumpwerk = 18002 + Klaeranlage = 18003 + AnlageKlaerschlamm = 18004 + SonstigeAbwasserBehandlungsanlage = 18005, XPlanVersion.FIVE_THREE + SalzOderSoleleitungen = 18006 + Regenwasser = 2000 + RegenwasserRueckhaltebecken = 20000 + Niederschlagswasserleitung = 20001 + Abfallentsorgung = 2200 + Muellumladestation = 22000 + Muellbeseitigungsanlage = 22001 + Muellsortieranlage = 22002 + Recyclinghof = 22003 + Ablagerung = 2400 + Erdaushubdeponie = 24000 + Bauschuttdeponie = 24000 + Hausmuelldeponie = 24000 + Sondermuelldeponie = 24000 + StillgelegteDeponie = 24000 + RekultivierteDeponie = 24000 + Telekommunikation = 2600 + Fernmeldeanlage = 26000 + Mobilfunkanlage = 26001 + Fernmeldekabel = 26002 + ErneuerbareEnergien = 2800 + KraftWaermeKopplung = 3000 + Sonstiges = 9999 + Produktenleitung = 99990 + + +class XP_ABEMassnahmenTypen(XPlanungEnumMixin, Enum): + """ Art der Maßnahme: Anpflanzung, Bindung, Erhaltung""" + + BindungErhaltung = 1000 + Anpflanzung = 2000 + # AnpflanzungBindungErhaltung = 3000 # TODO: does this even make sense? + + +class XP_AnpflanzungBindungErhaltungsGegenstand(XPlanungEnumMixin, Enum): + """ Gegenstand der Maßnahme: Anpflanzung, Bindung, Erhaltung""" + + Baeume = 1000 + Kopfbaeume = 1100 + Baumreihe = 1200 + Straeucher = 2000 + BaeumeUndStraeucher = 2050 + Hecke = 2100 + Knick = 2200 + SonstBepflanzung = 3000 + Gewaesser = 4000 + Fassadenbegruenung = 5000 + Dachbegruenung = 6000 + + +class XP_SPEZiele(XPlanungEnumMixin, Enum): + """ Gegenstand der Maßnahme: Anpflanzung, Bindung, Erhaltung""" + + SchutzPflege = 1000 + Entwicklung = 2000 + Anlage = 3000 + SchutzPflegeEntwicklung = 4000 + Sonstiges = 9999 + + +class XP_SPEMassnahmenTypen(XPlanungEnumMixin, Enum): + """ Gegenstand der Maßnahme: Anpflanzung, Bindung, Erhaltung""" + + def __new__(cls, *args, **kwds): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: int, version: XPlanVersion = None): + self._xplan_version = version + + @property + def version(self) -> XPlanVersion: + return self._xplan_version + + ArtenreicherGehoelzbestand = 1000 + NaturnaherWald = 1100 + ExtensivesGruenland = 1200 + Feuchtgruenland = 1300 + Obstwiese = 1400 + NaturnaherUferbereich = 1500 + Roehrichtzone = 1600 + Ackerrandstreifen = 1700 + Ackerbrache = 1800 + Gruenlandbrache = 1900 + Sukzessionsflaeche = 2000 + Hochstaudenflur = 2100 + Trockenrasen = 2200 + Heide = 2300 + Moor = 2400, XPlanVersion.SIX + Sonstiges = 9999 + + +class XP_Bundeslaender(XPlanungEnumMixin, Enum): + """ Zuständige Bundesländer für den Plan """ + + BB = 1000 + BE = 1200 + BW = 1200 + BY = 1300 + HB = 1400 + HE = 1500 + HH = 1600 + MV = 1700 + NI = 1800 + NW = 1900 + RP = 2000 + SH = 2100 + SL = 2200 + SN = 2300 + ST = 2400 + TH = 2500 + Bund = 3000 + + +class XP_ZweckbestimmungGemeinbedarf(XPlanungEnumMixin, Enum): + """ Erlaubte Nutzungsformen für den Gemeinbedarf """ + + def __new__(cls, *args, **kwds): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: int, version: XPlanVersion = None): + self._xplan_version = version + + @property + def version(self) -> XPlanVersion: + return self._xplan_version + + OeffentlicheVerwaltung = 1000 + KommunaleEinrichtung = 10000 + BetriebOeffentlZweckbestimmung = 10001 + AnlageBundLand = 10002 + BildungForschung = 1200 + Schule = 12000 + Hochschule = 12001 + BerufsbildendeSchule = 12002 + Forschungseinrichtung = 12003 + Kirche = 1400 + Sakralgebaeude = 14000 + KirchlicheVerwaltung = 14001 + Kirchengemeinde = 14002 + Sozial = 1600 + EinrichtungKinder = 16000 + EinrichtungJugendliche = 16001 + EinrichtungFamilienErwachsene = 16002 + EinrichtungSenioren = 16003 + SonstigeSozialeEinrichtung = 16004, XPlanVersion.FIVE_THREE + EinrichtungBehinderte = 16005 + Gesundheit = 1800 + Krankenhaus = 18000 + Kultur = 2000 + MusikTheater = 20000 + Bildung = 20001 + Sport = 2200 + Bad = 22000 + SportplatzSporthalle = 22001 + SicherheitOrdnung = 2400 + Feuerwehr = 24000 + Schutzbauwerk = 24001 + Justiz = 24002 + SonstigeSicherheitOrdnung = 24003, XPlanVersion.FIVE_THREE + Infrastruktur = 2600 + Post = 26000 + SonstigeInfrastruktur = 26001, XPlanVersion.FIVE_THREE + Sonstiges = 9999 + + +class XP_ZweckbestimmungSpielSportanlage(XPlanungEnumMixin, Enum): + """ Zweckbestimmungen für Spiel-Sportanlagen """ + + Sportanlage = 1000 + Spielanlage = 2000 + SpielSportanlage = 3000 + Sonstiges = 9999 + + +class XP_ZweckbestimmungGruen(XPlanungEnumMixin, Enum): + """ Zweckbestimmungen für Grünflächen """ + + def __new__(cls, *args, **kwds): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: int, version: XPlanVersion = None): + self._xplan_version = version + + @property + def version(self) -> XPlanVersion: + return self._xplan_version + + Parkanlage = 1000 + ParkanlageHistorisch = 10000 + ParkanlageNaturnah = 10001 + ParkanlageWaldcharakter = 10002 + NaturnaheUferParkanlage = 10003 + Dauerkleingarten = 1200 + ErholungsGaerten = 12000 + Sportplatz = 1400 + Reitsportanlage = 14000 + Hundesportanlage = 14001 + Wassersportanlage = 14002 + Schiessstand = 14003 + Golfplatz = 14004 + Skisport = 14005 + Tennisanlage = 14006 + Spielplatz = 1600 + Bolzplatz = 16000 + Abenteuerspielplatz = 16001 + Zeltplatz = 1800 + Campingplatz = 18000 + Badeplatz = 2000 + FreizeitErholung = 2200 + Kleintierhaltung = 22000 + Festplatz = 22001 + SpezGruenflaeche = 2400 + StrassenbegleitGruen = 24000 + BoeschungsFlaeche = 24001 + FeldWaldWiese = 24002, XPlanVersion.FIVE_THREE + Uferschutzstreifen = 24003 + Abschirmgruen = 24004 + UmweltbildungsparkSchaugatter = 24005 + RuhenderVerkehr = 24006 + Friedhof = 2600 + Naturerfahrungsraum = 2700, XPlanVersion.SIX + Sonstiges = 9999 + Gaertnerei = 99990 + + +class XP_Traegerschaft(XPlanungEnumMixin, Enum): + """ Trägerschaft einer Gemeinbedarfs-Fläche """ + + EinrichtungBund = 1000 + EinrichtungLand = 2000 + EinrichtungKreis = 3000 + KommunaleEinrichtung = 4000 + ReligioeserTraeger = 5000 + SonstTraeger = 6000 + + +class XP_Rechtscharakter(XPlanungEnumMixin, Enum): + """ Rechtliche Charakterisierung eines Planinhalts """ + + FestsetzungBPlan = 1000 + NachrichtlicheUebernahme = 2000 + DarstellungFPlan = 3000 + ZielDerRaumordnung = 4000 + GrundsatzDerRaumordnung = 4100 + NachrichtlicheUebernahmeZiel = 4200 + NachrichtlicheUebernahmeGrundsatz = 4300 + NurInformationsgehaltRPlan = 4400 + TextlichesZielRaumordnung = 4500 + ZielUndGrundsatzRaumordnung = 4600 + VorschlagRaumordnung = 4700 + FestsetzungImLP = 5000 + GeplanteFestsetzungImLP = 5100 + DarstellungKennzeichnungImLP = 5200 + LandschaftsplanungsInhaltZurBeruecksichtigung = 5300 + Hinweis = 6000 + Kennzeichnung = 7000 + Vermerk = 8000 + Unbekannt = 9998 + Sonstiges = 9999 + + def to_fp_rechtscharakter(self) -> FP_Rechtscharakter: + if self.value == 3000: + return FP_Rechtscharakter.Darstellung + elif self.value == 4200 or self.value == 4300: + return FP_Rechtscharakter.NachrichtlicheUebernahme + elif self.value == 6000: + return FP_Rechtscharakter.Hinweis + elif self.value == 8000: + return FP_Rechtscharakter.Vermerk + + +class XP_ArtHoehenbezug(XPlanungEnumMixin, Enum): + """ Art des Höhenbezuges """ + + def __new__(cls, *args, **kwds): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: int, version: XPlanVersion = None): + self._xplan_version = version + + @property + def version(self) -> XPlanVersion: + return self._xplan_version + + absolutNHN = 1000 + absolutNN = 1100 + absolutDHHN = 1200 + relativGelaendeoberkante = 2000 + relativGehwegOberkante = 2500 + relativBezugshoehe = 3000 + relativStrasse = 3500 + relativEFH = 4000, XPlanVersion.SIX + + +class XP_ArtHoehenbezugspunkt(XPlanungEnumMixin, Enum): + """ Bestimmung des Bezugspunktes der Höhenangaben """ + + TH = 1000 + FH = 2000 + OK = 3000 + LH = 3500 + SH = 4000 + EFH = 4500 + HBA = 5000 + UK = 5500 + GBH = 6000 + WH = 6500 + GOK = 6600 diff --git a/src/SAGisXPlanung/XPlan/feature_types.py b/src/SAGisXPlanung/XPlan/feature_types.py new file mode 100644 index 0000000..cad3617 --- /dev/null +++ b/src/SAGisXPlanung/XPlan/feature_types.py @@ -0,0 +1,330 @@ +import logging +from uuid import uuid4 + +from geoalchemy2 import Geometry, WKTElement +from sqlalchemy import Column, String, Date, Integer, Float, Enum, ForeignKey, event, CheckConstraint +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from qgis.core import QgsCoordinateReferenceSystem, QgsGeometry, QgsVectorLayer, QgsFeatureRequest, edit + +from .XP_Praesentationsobjekte.feature_types import XP_Nutzungsschablone +from .conversions import XP_Rechtscharakter_EnumType +from .core import XPCol +from .enums import XP_BedeutungenBereich, XP_Rechtsstand, XP_Rechtscharakter +from SAGisXPlanung import Base, XPlanVersion +from SAGisXPlanung.GML.geometry import geometry_from_spatial_element +from SAGisXPlanung.config import export_version +from .mixins import ElementOrderMixin, PolygonGeometry, MapCanvasMixin, RelationshipMixin +from .types import LargeString, Angle, Length +from ..MapLayerRegistry import MapLayerRegistry + +logger = logging.getLogger(__name__) + + +class XP_Plan(PolygonGeometry, ElementOrderMixin, RelationshipMixin, MapCanvasMixin, Base): + """ Abstrakte Oberklasse für alle Klassen raumbezogener Pläne. """ + + __tablename__ = 'xp_plan' + + __readonly_columns__ = ['raeumlicherGeltungsbereich'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + type = Column(String(50)) + + name = Column(String, nullable=False, doc='Name') + nummer = Column(String, doc='Nummer') + internalId = Column(String, doc='Interner Idenfikator') + beschreibung = Column(LargeString, doc='Beschreibung') + kommentar = Column(LargeString, doc='Kommentar') + technHerstellDatum = Column(Date, doc='Datum der technischen Ausfertigung') + genehmigungsDatum = Column(Date, doc='Datum der Genehmigung') + untergangsDatum = Column(Date, doc='Untergangsdatum') + erstellungsMassstab = Column(Integer, doc='Erstellungsmaßstab') + bezugshoehe = Column(Float, doc='Standard Bezugshöhe') + hoehenbezug = XPCol(String(), doc='Höhenbezug', version=XPlanVersion.SIX) + technischerPlanersteller = Column(String, doc='Technischer Planersteller') + raeumlicherGeltungsbereich = Column(Geometry(), + CheckConstraint('st_dimension("raeumlicherGeltungsbereich")) = 2', + name='check_geometry_type'), + nullable=False, doc='Geltungsbereich') + + verfahrensMerkmale = relationship("XP_VerfahrensMerkmal", back_populates="plan", cascade="all, delete", + passive_deletes=True, doc='Verfahrensmerkmale') + externeReferenz = relationship("XP_SpezExterneReferenz", back_populates="plan", cascade="all, delete", + passive_deletes=True, doc='Externe Referenzen') + + __mapper_args__ = { + "polymorphic_identity": "xp_plan", + "polymorphic_on": type, + } + + @classmethod + def avoid_export(cls): + return ['plangeber_id'] + + def displayName(self): + return 'Geltungsbereich' + + def srs(self) -> QgsCoordinateReferenceSystem: + return QgsCoordinateReferenceSystem(f'EPSG:{self.raeumlicherGeltungsbereich.srid}') + + def geometry(self) -> QgsGeometry: + return geometry_from_spatial_element(self.raeumlicherGeltungsbereich) + + def setGeometry(self, geom: QgsGeometry): + self.raeumlicherGeltungsbereich = WKTElement(geom.asWkt(), srid=self.raeumlicherGeltungsbereich.srid) + + def toCanvas(self, layer_group, plan_xid=None): + MapLayerRegistry().removeCanvasItems(str(self.id)) + + super(XP_Plan, self).toCanvas(layer_group, plan_xid) + + def enforceFlaechenschluss(self): + """ Abstrakte Methode zum Erzwingen des Flächenschluss. Muss in jeder konkreten Klasse implementiert werden.""" + raise NotImplementedError() + + @classmethod + @property + def bereich(cls): + raise NotImplementedError() + + def edit_widget(self): + from SAGisXPlanung.gui.widgets.QXPlanTabWidget import QXPlanTabWidget + + tabs = QXPlanTabWidget(self.__class__) + + def iterate_relations(obj, parent_class=None, page=0): + for attr in obj.__class__.element_order(version=export_version()): + attribute_value = getattr(obj, attr) + if not attribute_value: + continue + + rel = next((obj for obj in obj.relationships() if obj[0] == attr), None) + if rel is not None: + class_type = rel[1].mapper.class_ + if class_type == parent_class or ( + parent_class is not None and issubclass(parent_class, class_type)): + continue + if hasattr(obj.__class__, '__avoidRelation__') and rel[0] in obj.__class__.__avoidRelation__: + continue + if class_type == XP_Objekt or issubclass(class_type, XP_Objekt): + continue + if next(iter(rel[1].remote_side)).primary_key or rel[1].secondary is not None: + input_element = tabs.widget(page).fields.get(attr, None) + if input_element: + input_element.setDefault(attribute_value) + continue + if not rel[1].uselist: + attribute_value = [attribute_value] + for rel_obj in attribute_value: + tabs.setCurrentIndex(page) + tabs.createTab(class_type, obj.__class__, parent_attribute=attr, existing_xid=str(rel_obj.id)) + p = next(index for index in range(tabs.count()) if class_type.__name__ == tabs.tabText(index)) + iterate_relations(rel_obj, obj.__class__, page=p) + continue + + # this can raise KeyError on columns that aren't displayed in forms + try: + input_element = tabs.widget(page).fields[attr] + input_element.setDefault(attribute_value) + except KeyError: + pass + + iterate_relations(self) + tabs.setCurrentIndex(0) + return tabs + + +class XP_Bereich(PolygonGeometry, ElementOrderMixin, RelationshipMixin, MapCanvasMixin, Base): + """ XP_Bereich + + Abstrakte Oberklasse für die Modellierung von Bereichen. + Ein Bereich fasst die Inhalte eines Plans nach bestimmten Kriterien zusammen. + """ + + __tablename__ = 'xp_bereich' + __avoidRelation__ = ['planinhalt', 'praesentationsobjekt', 'simple_geometry'] + __readonly_columns__ = ['geltungsbereich'] + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + type = Column(String(50)) + + nummer = Column(Integer, nullable=False, doc='Nummer') + name = Column(String, doc='Name') + bedeutung = Column(Enum(XP_BedeutungenBereich), doc='Bedeutung') + detaillierteBedeutung = Column(LargeString, doc='Erklärung der Bedeutung') + erstellungsMassstab = Column(Integer, doc='Erstellungsmaßstab') + geltungsbereich = Column(Geometry(), + CheckConstraint('st_dimension("raeumlicherGeltungsbereich")) = 2', + name='check_geometry_type'), + doc='Geltungsbereich') + + refScan = relationship("XP_ExterneReferenz", back_populates="bereich", cascade="all, delete", + passive_deletes=True, doc='Externe Referenz') + planinhalt = relationship("XP_Objekt", back_populates="gehoertZuBereich", cascade="all, delete", + passive_deletes=True) + praesentationsobjekt = relationship("XP_AbstraktesPraesentationsobjekt", back_populates="gehoertZuBereich", + cascade="all, delete", passive_deletes=True) + + # non XPlanung attributes + simple_geometry = relationship("XP_SimpleGeometry", back_populates="gehoertZuBereich", cascade="all, delete", + passive_deletes=True) + + __mapper_args__ = { + "polymorphic_identity": "xp_bereich", + "polymorphic_on": type, + } + + def displayName(self): + if not self.name: + return f'Bereich {self.nummer}' + return f'Bereich {self.nummer} - {self.name}' + + def srs(self) -> QgsCoordinateReferenceSystem: + return QgsCoordinateReferenceSystem(f'EPSG:{self.geltungsbereich.srid}') + + def geometry(self) -> QgsGeometry: + return geometry_from_spatial_element(self.geltungsbereich) + + def setGeometry(self, geom: QgsGeometry): + self.geltungsbereich = WKTElement(geom.asWkt(), srid=self.geltungsbereich.srid) + + def toCanvas(self, layer_group, plan_xid=None): + """ Override custom toCanvas behaviour, because each XP_Bereich object should get its own layer + instead of merging geometries as feautures into one layer""" + if plan_xid is None: + raise ValueError("plan_xid cant be None when displaying XP_Bereich") + + try: + srs = self.srs().postgisSrid() + except: + # when srs fails, plan content can't be displayed, therefore return early + return + layer = MapLayerRegistry().layerByFeature(str(self.id)) + if not layer: + layer = self.asLayer(srs, plan_xid, name=self.displayName(), geom_type=self.__geometry_type__) + + feat_id = self.addFeatureToLayer(layer, self.asFeature(layer.fields())) + layer.setCustomProperty(f'xplanung/feat-{feat_id}', str(self.id)) + MapLayerRegistry().addLayer(layer, group=layer_group) + + @classmethod + def hidden_inputs(cls): + return ['praesentationsobjekt', 'simple_geometry'] + + @classmethod + def avoid_export(cls): + return ['praesentationsobjekt', 'simple_geometry'] + + +class XP_Objekt(RelationshipMixin, ElementOrderMixin, MapCanvasMixin, Base): + """Abstrakte Oberklasse für alle XPlanung - Fachobjekte """ + + __tablename__ = 'xp_objekt' + __avoidRelation__ = ['wirdDargestelltDurch'] + + hidden = False # if class should be hidden in GUI forms + annotation_delete_queue = [] # stores all annotation items that should be removed from canvas after a delete + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + type = Column(String(50)) + + uuid = Column(String) + text = Column(String) + rechtsstand = Column(Enum(XP_Rechtsstand)) + + gesetzlicheGrundlage_id = XPCol(UUID(as_uuid=True), ForeignKey('xp_gesetzliche_grundlage.id'), + version=XPlanVersion.SIX, attribute='gesetzlicheGrundlage') + gesetzlicheGrundlage = relationship("XP_GesetzlicheGrundlage", back_populates="xp_objekts") + + gliederung1 = Column(String) + gliederung2 = Column(String) + ebene = Column(Integer) + + hoehenangabe = relationship("XP_Hoehenangabe", back_populates="xp_objekt", cascade="all, delete", + passive_deletes=True) + + gehoertZuBereich_id = Column(UUID(as_uuid=True), ForeignKey('xp_bereich.id', ondelete='CASCADE')) + gehoertZuBereich = relationship('XP_Bereich', back_populates='planinhalt') + + wirdDargestelltDurch = relationship("XP_AbstraktesPraesentationsobjekt", back_populates="dientZurDarstellungVon", + cascade="all, delete", passive_deletes=True) + + aufschrift = Column(String) + rechtscharakter = XPCol(XP_Rechtscharakter_EnumType(XP_Rechtscharakter), nullable=False, doc='Rechtscharakter', + version=XPlanVersion.SIX) + + # non xplanung attributes + drehwinkel = Column(Angle, default=0) + skalierung = Column(Length, default=0.5) + + __mapper_args__ = { + "polymorphic_identity": "xp_objekt", + "polymorphic_on": type, + } + + # def __setattr__(self, key, value): + # if key == 'rechtscharakter' and export_version() == XPlanVersion.FIVE_THREE: + # if not isinstance(value, XP_Rechtscharakter): + # logger.debug(f'setattr returns {value.to_xp_rechtscharakter()}') + # return super().__setattr__(key, value.to_xp_rechtscharakter()) + # return super().__setattr__(key, value) + # else: + # return super().__setattr__(key, value) + + @classmethod + def hidden_inputs(cls): + return ['drehwinkel', 'skalierung', 'xp_versions'] + + @classmethod + def avoid_export(cls): + return ['hidden', 'drehwinkel', 'skalierung', 'gesetzlicheGrundlage_id', 'annotation_delete_queue', + 'xp_versions'] + + def displayName(self): + return self.__class__.__name__ + + def toCanvas(self, layer_group, plan_xid=None): + + # display all associated annotation items + for po in self.wirdDargestelltDurch: + if isinstance(po, XP_Nutzungsschablone): + continue + try: + po.toCanvas(layer_group, plan_xid) + except TypeError: + pass + + super(XP_Objekt, self).toCanvas(layer_group, plan_xid) + + +@event.listens_for(XP_Objekt, 'before_delete', propagate=True) +def receive_before_delete(mapper, connection, target: XP_Objekt): + """ Removes feature from canvas if it is currently visible """ + # populate the delete queue, the items get consumed in `receive_after_delete` to remove all visible annotations + target.annotation_delete_queue = target.wirdDargestelltDurch + + +@event.listens_for(XP_Objekt, 'after_delete', propagate=True) +def receive_after_delete(mapper, connection, target: XP_Objekt): + """ Removes feature from canvas if it is currently visible """ + + layer: QgsVectorLayer = MapLayerRegistry().layerByFeature(str(target.id)) + if not layer: + return + + fr = QgsFeatureRequest().setNoAttributes().setFlags(QgsFeatureRequest.NoGeometry) + for feature in layer.getFeatures(fr): + id_prop = layer.customProperties().value(f'xplanung/feat-{feature.id()}') + if id_prop == str(target.id): + with edit(layer): + res = layer.deleteFeature(feature.id()) + if not res: + logger.warning(f'{target.displayName()}:{target.id} Feature wurde nicht von der Karte entfernt') + + while len(target.annotation_delete_queue) > 0: + po = target.annotation_delete_queue.pop(0) + po.remove_from_canvas() + + break diff --git a/src/SAGisXPlanung/XPlan/mixins.py b/src/SAGisXPlanung/XPlan/mixins.py new file mode 100644 index 0000000..c1ba0f6 --- /dev/null +++ b/src/SAGisXPlanung/XPlan/mixins.py @@ -0,0 +1,304 @@ +import logging +from inspect import signature +from typing import Tuple, Union, Any, Iterator, Iterable + +from qgis.core import (QgsFields, QgsFeature, QgsVectorLayer, QgsField, QgsEditorWidgetSetup, QgsAnnotationLayer, + QgsWkbTypes) +from qgis.PyQt.QtCore import QVariant +from sqlalchemy import inspect +from sqlalchemy.orm import class_mapper, RelationshipProperty + +from SAGisXPlanung import XPlanVersion +from SAGisXPlanung.XPlan.core import XPCol, XPRelationshipProperty +from SAGisXPlanung.GML.geometry import geom_type_as_layer_url +from SAGisXPlanung.XPlanungItem import XPlanungItem + +from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry +from SAGisXPlanung.config import xplan_tooltip, export_version + +logger = logging.getLogger(__name__) + + +class RelationshipMixin: + """ Mixin zum Abruf aller Beziehungen eines ORM-Objekts im XPlanung Objektmodell """ + + @classmethod + def relationships(cls): + return cls.__mapper__.relationships.items() + + def related(self) -> Iterator[Any]: + """ Iterator über alle abhängigen Objekte""" + for rel in self.__class__.relationships(): + if next(iter(rel[1].remote_side)).primary_key or rel[1].secondary is not None: + continue + if not self.__class__.relation_fits_version(rel[0], export_version()): + continue + rel_items = getattr(self, rel[0]) + if rel_items is None: + continue + # force iterable, if relation is one-to-one + if not isinstance(rel_items, Iterable): + rel_items = [rel_items] + + yield from rel_items + + @classmethod + def relation_prop_display(cls, rel: Tuple[str, RelationshipProperty]) -> Tuple[str, Union[None, str]]: + """ + Gibt Displayname und Tooltip für ein gegebenes RelationshipProperty zurück. + + Returns + ------- + str, str: + Displayname, Tooltip + """ + if not hasattr(cls, 'xp_relationship_properties') or not next((p for p in cls.xp_relationship_properties() if p.rel_name == rel[0]), None): + if rel[1].doc: + return rel[1].doc, f'XPlanung-Attribut: {rel[0]}' + else: + return rel[0], xplan_tooltip(cls, rel[0]) + + for relationship_property in cls.xp_relationship_properties(): # type: XPRelationshipProperty + if relationship_property.rel_name == rel[0]: + return relationship_property.xplan_attribute, xplan_tooltip(cls, relationship_property.xplan_attribute) + + raise Exception(f'Could not determine display parameters for relation {rel[0]} {rel[1]}') + + +class GeometryObject: + pass + + +class MixedGeometry(GeometryObject): + """ Mixin zum Klassifizieren von Klassen mit variablem Raumbezug (Geometrieobjekte im XPlan-Schema) """ + __geometry_type__ = QgsWkbTypes.UnknownGeometry + + +class PointGeometry(GeometryObject): + """ Mixin zum Klassifizieren von Klassen als Flächengeometrien""" + __geometry_type__ = QgsWkbTypes.PointGeometry + + @classmethod + def hidden_inputs(cls): + h = super(PointGeometry, cls).hidden_inputs() + return h + ['flaechenschluss'] + + @classmethod + def avoid_export(cls): + h = super(PointGeometry, cls).avoid_export() + return h + ['flaechenschluss'] + + +class PolygonGeometry(GeometryObject): + """ Mixin zum Klassifizieren von Klassen als Flächengeometrien""" + __geometry_type__ = QgsWkbTypes.PolygonGeometry + + +class LineGeometry: + """ Mixin zum Klassifizieren von Klassen als Liniengeometrien""" + __geometry_type__ = QgsWkbTypes.LineGeometry + + @classmethod + def hidden_inputs(cls): + h = super(LineGeometry, cls).hidden_inputs() + return h + ['flaechenschluss'] + + @classmethod + def avoid_export(cls): + h = super(LineGeometry, cls).avoid_export() + return h + ['flaechenschluss'] + + +class FlaechenschlussObjekt: + """ Mixin, zum Deklarieren eines Flächenschlussobjekts. + Alle Objektklassen die von *P_Flaechenobjekt/*P_Flaechenschlussobjekt müssen mit diesem Mixin dekoriert werden, + damit der schema-konforme Export ins XPlanGML gewährleistet ist. + + Flächenschlussobjekte zeichnen sich dadurch aus, dass das Attribut `flaechenschluss` erforderlich ist.""" + pass + + +class ElementOrderDeclarativeInheritanceFixMixin: + """ Mixin, zum Dekorieren einer sqlalchemy @declarative_base Klasse, damit die `element_order` Methode korrekt + funktioniert. + Dieses Mixin muss immmer zuletzt in der Klassensignatur angewendet werden. """ + is_declarative_base = True + + +class ElementOrderMixin: + """ Mixin, dass eine Methode zur Auflösung der Reihenfolge der Attribute jedes XPlanung-Objekts bietet. + Ist das XPlanung-Objekt in eine Vererbungshierarchie eingebunden, muss die XPlanung-Basisklasse in der + Deklaration aller Basisklassen immer zuerst stehen, damit die Methode element_order korrekt funktioniert. """ + + @classmethod + def element_order(cls, include_base=True, only_columns=False, export=True, version=XPlanVersion.FIVE_THREE): + if only_columns: + order = inspect(cls).columns.keys() + order = [cls.normalize_column_name(x) for x in order if cls.attr_is_treated_as_column(x)] + + # remove duplicates + order = list(dict.fromkeys(order)) + else: + order = [key for key, value in cls.__dict__.items() if + not (key.startswith('__') or key.startswith('_sa_') or callable(value) + or isinstance(value, classmethod))] + + if version is not None: + order = [x for x in order if cls.attr_fits_version(x, version)] + + if not export and hasattr(cls, 'hidden_inputs'): + order = [x for x in order if x not in cls.hidden_inputs()] + elif export and hasattr(cls, 'avoid_export'): + order = [x for x in order if x not in cls.avoid_export()] + + # remove sqlalchemy utility attributes + order = [x for x in order if x not in ['type', 'id']] + + try: + bases = cls.__bases__[-1] + base_order = bases.element_order(only_columns=only_columns, export=export, version=version) + if not include_base and not hasattr(cls, 'is_declarative_base'): + return [x for x in order if x not in base_order] + # why was this check even introduced? + # if only_columns: + # return order + return base_order + [x for x in order if x not in base_order] + except Exception as e: + return order + + @classmethod + def attr_fits_version(cls, attr_name: str, version: XPlanVersion) -> bool: + """ Überprüft, ob ein XPlanung-Attribut zur gegebenen Version des Standards gehört""" + attr = getattr(cls, attr_name) + if hasattr(attr, "version") and attr.version != version: + return False + if hasattr(cls, 'xp_relationship_properties'): + for relationship_property in cls.xp_relationship_properties(): # type: XPRelationshipProperty + if relationship_property.rel_name == attr_name and relationship_property.allowed_version != version: + return False + return True + + @classmethod + def relation_fits_version(cls, rel_name: str, version: XPlanVersion) -> bool: + """ Überprüft, ob eine XPlanung-Relation zur gegebenen Version des Standards gehört""" + if hasattr(cls, 'xp_relationship_properties'): + for relationship_property in cls.xp_relationship_properties(): # type: XPRelationshipProperty + if relationship_property.rel_name == rel_name and relationship_property.allowed_version != version: + return False + return True + + @classmethod + def attr_is_treated_as_column(cls, attr_name: str, consider_suffix=True) -> bool: + """ Überprüft ob ein gegebenes Attribut, als Column (statt Relationship) gehandhabt wird.""" + attr = getattr(cls, attr_name) + if hasattr(attr, "version") and hasattr(attr, 'attribute') and attr.attribute is not None: + return True + if consider_suffix: + return not attr_name.endswith('_id') + return False + + @classmethod + def normalize_column_name(cls, attr_name: str) -> str: + """ Gibt den 'echten' XPlanung-Namen züruck, wenn das Python-Attribut nicht korrekt bennant werden kann. + XPCol muss dafür mit Parameter `attribute` verwendet werden.""" + attr = getattr(cls, attr_name) + if hasattr(attr, "version") and hasattr(attr, 'attribute'): + return attr.attribute or attr_name + return attr_name + + +class XPlanungEnumMixin: + def __str__(self): + return str(self.name) + + +class MapCanvasMixin: + """ Mixin, dass Methoden zum Darstellen von Planinhalten auf dem MapCanvas bietet. + Kann nur in Kombination mit einem GeometryMixin verwendet werden! + Implementierende Klasse muss über die `geometry()`-Methode verfügen! """ + + def toCanvas(self, layer_group, plan_xid=None): + from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry + + try: + srs = self.srs().postgisSrid() + except Exception as e: + # when srs fails, plan content can't be displayed, therefore return early + logger.error(f'Fehler beim Abruf des Koordinatebezugssystems. Layer kann nicht dargestellt werden. {e}') + return + + # check for new geometry API (introduced v1.10.2) or take previously __geometry_type__ attr + if hasattr(self, 'geomType'): + geom_type = self.geomType() + else: + geom_type = self.__geometry_type__ + + plan_id = str(plan_xid) if plan_xid else str(self.id) + layer = MapLayerRegistry().layerByXid(XPlanungItem(xid=self.id, xtype=self.__class__, plan_xid=plan_id), + geom_type=geom_type) + if not layer: + layer = self.asLayer(srs, plan_id, name=self.displayName(), geom_type=geom_type) + + feat_id = None + if isinstance(layer, QgsVectorLayer): + feat_id = self.addFeatureToLayer(layer, self.asFeature(layer.fields())) + elif isinstance(layer, QgsAnnotationLayer): + feat_id = layer.addItem(self.asFeature()) + layer.setCustomProperty(f'xplanung/feat-{feat_id}', str(self.id)) + MapLayerRegistry().addLayer(layer, group=layer_group) + + def asFeature(self, fields: QgsFields = None) -> QgsFeature: + feat = QgsFeature() + feat.setGeometry(self.geometry()) + feat.setFields(fields) + if hasattr(self, 'layer_fields'): + for field, value in self.layer_fields().items(): + feat[field] = value + return feat + + def addFeatureToLayer(self, layer, feat): + dp = layer.dataProvider() + layer.startEditing() + _, newFeatures = dp.addFeatures([feat]) + layer.commitChanges() + + return newFeatures[0].id() + + @classmethod + def asLayer(cls, srid, plan_xid, name=None, geom_type=None) -> QgsVectorLayer: + geom_type = geom_type if geom_type is not None else cls.__geometry_type__ + layer = QgsVectorLayer(f"{geom_type_as_layer_url(geom_type)}?crs=EPSG:{srid}", + cls.__name__ if not name else name, "memory") + layer.setCustomProperty("skipMemoryLayersCheck", 1) + layer.setCustomProperty('xplanung/type', cls.__name__) + layer.setCustomProperty('xplanung/plan-xid', str(plan_xid)) + + from SAGisXPlanung.XPlan.feature_types import XP_Objekt + + if issubclass(cls, XP_Objekt): + layer.setReadOnly(True) + + if hasattr(cls, 'renderer'): + if signature(cls.renderer).parameters.get("geom_type"): + layer.setRenderer(cls.renderer(geom_type)) + else: + layer.setRenderer(cls.renderer()) + + if hasattr(cls, 'attributes'): + setup = QgsEditorWidgetSetup('Hidden', {}) + + for field in cls.attributes(): + layer.dataProvider().addAttributes([QgsField(field, QVariant.String, 'string')]) + layer.updateFields() + + # hide field in editor widget + i = layer.fields().indexFromName(field) + layer.setEditorWidgetSetup(i, setup) + + # hide all fields in attribute table + cfg = layer.attributeTableConfig() + for i in range(cfg.size()): + cfg.setColumnHidden(i, True) + layer.setAttributeTableConfig(cfg) + + return layer diff --git a/src/SAGisXPlanung/XPlan/simple_depth.py b/src/SAGisXPlanung/XPlan/simple_depth.py new file mode 100644 index 0000000..c1ffd5f --- /dev/null +++ b/src/SAGisXPlanung/XPlan/simple_depth.py @@ -0,0 +1,129 @@ +from enum import Enum +from uuid import uuid4 + +from geoalchemy2 import Geometry +from geoalchemy2.shape import to_shape +from sqlalchemy import Column, String, ForeignKey +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship + +from qgis.core import QgsCoordinateReferenceSystem, QgsGeometry + +from SAGisXPlanung import Base +from SAGisXPlanung.GML.geometry import geometry_from_spatial_element +from SAGisXPlanung.XPlan.mixins import RelationshipMixin, MapCanvasMixin + + +class XP_SimpleGeometry(Base, RelationshipMixin, MapCanvasMixin): + """ Klasse zum Erfassen von vektoriellen Planinhalten mit einfacher Erfassungstiefe """ + + __tablename__ = 'xp_simple_geometry' + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + + name = Column(String) + xplanung_type = Column(String) + position = Column(Geometry()) + + gehoertZuBereich_id = Column(UUID(as_uuid=True), ForeignKey('xp_bereich.id', ondelete='CASCADE')) + gehoertZuBereich = relationship('XP_Bereich', back_populates='simple_geometry') + + @property + def __geometry_type__(self): + return str(to_shape(self.position).type) + + def displayName(self): + return self.xplanung_type + + def srs(self) -> QgsCoordinateReferenceSystem: + return QgsCoordinateReferenceSystem(f'EPSG:{self.position.srid}') + + def geometry(self) -> QgsGeometry: + return geometry_from_spatial_element(self.position) + + +class XPlanungPointTypes(Enum): + """ XPlanung Objekttypen für die einfache Erfassungstiefe: Punktgeometrien""" + + BP_ZusatzkontingentLaerm = '2.6.5' + BP_AnpflanzungBindungErhaltung = '2.8.1' + BP_FestsetzungNachLandesrecht = '2.9.2' + BP_EinfahrtPunkt = '2.12.2' + + +class XPlanungLineTypes(Enum): + """ XPlanung Objekttypen für die einfache Erfassungstiefe: Liniengeometrien""" + + BP_AbweichungVonBaugrenze = '2.3.2' + BP_BauGrenze = '2.3.5' + BP_BauLinie = '2.3.6' + BP_FirstRichtungsLinie = '2.3.9' + BP_RichtungssektorGrenze = '2.6.4' + BP_AnpflanzungBindungErhaltung = '2.8.1' + BP_AbstandsMass = '2.9.1' + BP_FestsetzungNachLandesrecht = '2.9.2' + BP_NutzungsartenGrenze = '2.9.8' + BP_Wegerecht = '2.9.13' + BP_VerEntsorgung = '2.11.1' + BP_BereichOhneEinAusfahrtLinie = '2.12.1' + BP_EinfahrtsbereichLinie = '2.12.3' + BP_StrassenbegrenzungsLinie = '2.12.4' + BP_Strassenkoerper = '2.12.5' + + +class XPlanungPolygonTypes(Enum): + """ XPlanung Objekttypen für die einfache Erfassungstiefe: Flächengeometrien""" + + BP_AbgrabungsFlaeche = '2.2.1' + BP_AufschuettungsFlaeche = '2.2.2' + BP_BodenschaetzeFlaeche = '2.2.3' + BP_RekultivierungsFlaeche = '2.2.4' + BP_AbstandsFlaeche = '2.3.1' + BP_AbweichungVonUeberbaubererGrundstuecksFlaeche = '2.3.3' + BP_BaugebietsTeilFlaeche = '2.3.4' + BP_BesondererNutzungszweckFlaeche = '2.3.7' + BP_FoerderungsFlaeche = '2.3.10' + BP_GebaeudeFlaeche = '2.3.11' + BP_GemeinschaftsanlagenFlaeche = '2.3.12' + BP_NebenanlagenAusschlussFlaeche = '2.3.14' + BP_NebenanlagenFlaeche = '2.3.15' + BP_NichtUeberbaubareGrundstuecksflaeche = '2.3.16' + BP_PersGruppenBestimmteFlaeche = '2.3.17' + BP_RegelungVergnuegungsstaetten = '2.3.18' + BP_SpezielleBauweise = '2.3.19' + BP_UeberbaubareGrundstuecksFlaeche = '2.3.20' + BP_ErhaltungsBereichFlaeche = '2.4.1' + BP_GemeinbedarfsFlaeche = '2.5.1' + BP_SpielSportanlagenFlaeche = '2.5.2' + BP_ZusatzkontingentLaermFlaeche = '2.6.6' + BP_GruenFlaeche = '2.7.1' + BP_KleintierhaltungFlaeche = '2.7.2' + BP_Landwirtschaft = '2.7.3' + BP_LandwirtschaftsFlaeche = '2.7.4' + BP_WaldFlaeche = '2.7.5' + BP_AnpflanzungBindungErhaltung = '2.8.1' + BP_AusgleichsFlaeche = '2.8.2' + BP_EingriffsBereich = '2.8.4' + BP_SchutzPflegeEntwicklungsFlaeche = '2.8.5' + BP_AbstandsMass = '2.9.1' + BP_FestsetzungNachLandesrecht = '2.9.2' + BP_FlaecheOhneFestsetzung = '2.9.3' + BP_FreiFlaeche = '2.9.4' + BP_KennzeichnungsFlaeche = '2.9.7' + BP_Sichtflaeche = '2.9.9' + BP_TextlicheFestsetzungsFlaeche = '2.9.10' + BP_Veraenderungssperre = '2.9.12' + BP_Wegerecht = '2.9.13' + BP_Immissionsschutz = '2.10.1' + BP_TechnischeMassnahmenFlaeche = '2.10.2' + BP_VerEntsorgung = '2.11.1' + BP_ZentralerVersorgungsbereich = '2.11.1' + BP_Strassenkoerper = '2.12.5' + BP_StrassenVerkehrsFlaeche = '2.12.6' + BP_VerkehrsflaecheBesondererZweckbestimmung = '2.12.7' + BP_GewaesserFlaeche = '2.13.1' + BP_WasserwirtschaftsFlaeche = '2.13.2' + + + + diff --git a/src/SAGisXPlanung/XPlan/types.py b/src/SAGisXPlanung/XPlan/types.py new file mode 100644 index 0000000..2ea37ec --- /dev/null +++ b/src/SAGisXPlanung/XPlan/types.py @@ -0,0 +1,127 @@ +import logging +import sys + +from qgis.core import Qgis, QgsWkbTypes +from sqlalchemy import types + +logger = logging.getLogger(__name__) + +if Qgis.versionInt() >= 33000: + GeometryType = Qgis.GeometryType +else: + GeometryType = QgsWkbTypes.PointGeometry + + +class ConformityException(Exception): + def __init__(self, message, code, object_type): + self.code = code + self.message = message + self.object_type = object_type + super(ConformityException, self).__init__(message) + + +class InvalidFormException(Exception): + pass + + +class XPlanungMeasureType: + pass + + +class LargeString(types.TypeDecorator): + """ Datentyp für lange textliche Beschreibungen """ + impl = types.String + python_type = str + cache_ok = True + + +class RefURL(types.TypeDecorator): + """ Datentyp für URLs zu externen Referenzen """ + + impl = types.String + python_type = str + cache_ok = True + + +class Angle(types.TypeDecorator, XPlanungMeasureType): + """ Repräsentation des XPlanGML AngleType """ + + impl = types.Integer + python_type = float + cache_ok = True + + UOM = 'grad' + MIN_VALUE = 0 + MAX_VALUE = 360 + + +class Area(types.TypeDecorator, XPlanungMeasureType): + """ Repräsentation des XPlanGML AreaType """ + + impl = types.Float + python_type = float + cache_ok = True + + UOM = 'm2' + MIN_VALUE = 0 + MAX_VALUE = sys.float_info.max + + +class Volume(types.TypeDecorator, XPlanungMeasureType): + """ Repräsentation des XPlanGML VolumeType """ + + impl = types.Float + python_type = float + cache_ok = True + + UOM = 'm3' + MIN_VALUE = 0 + MAX_VALUE = sys.float_info.max + + +class Length(types.TypeDecorator, XPlanungMeasureType): + """ Repräsentation des XPlanGML LengthType """ + + impl = types.Float + python_type = float + cache_ok = True + + UOM = 'm' + MIN_VALUE = 0 + MAX_VALUE = sys.float_info.max + + +class Scale(types.TypeDecorator, XPlanungMeasureType): + """ Repräsentation des XPlanGML ScaleType """ + + impl = types.Integer + python_type = int + cache_ok = True + + UOM = '%' + MIN_VALUE = 0 + MAX_VALUE = 100 + + +class RegExString(types.TypeDecorator): + """ String-Datentyp, mit vorgegebenem RegEx-Muster """ + + def __init__(self, expression, *args, error_msg=None, **kwargs): + self.expression = expression + self.error_msg = error_msg + super(RegExString, self).__init__(args, kwargs) + + impl = types.String + python_type = str + cache_ok = True + + +class XPEnum(types.TypeDecorator): + """ Enum-Datentyp der Information über die Möglichkeit der Anzeige eines Platzhalters enthält""" + + def __init__(self, *args, include_default=None, **kwargs): + self.include_default = include_default + super(XPEnum, self).__init__(*args, **kwargs) + + impl = types.Enum + cache_ok = True diff --git a/src/SAGisXPlanung/XPlanungItem.py b/src/SAGisXPlanung/XPlanungItem.py new file mode 100644 index 0000000..1aaaad2 --- /dev/null +++ b/src/SAGisXPlanung/XPlanungItem.py @@ -0,0 +1,16 @@ +from dataclasses import dataclass + +from qgis.core import QgsWkbTypes + + +@dataclass +class XPlanungItem: + """ Container für ein XPlanung-Objekt. Beeinhaltet die XPlanung-ID des Objekts und die Objektart. + Hält bei Bedarf zusätzlich Referenzen auf den zugehörigen Plan und Bereich. + Nützlich als Parameter zur Übergabe von XPlanung-Objekt, um nicht auf den Datenbank-Objekten zu operieren.""" + xid: str + xtype: type + plan_xid: str = None + bereich_xid: str = None + parent_xid: str = None + geom_type: QgsWkbTypes.GeometryType = None diff --git a/src/SAGisXPlanung/XPlanungPlugin.py b/src/SAGisXPlanung/XPlanungPlugin.py new file mode 100644 index 0000000..fc158f6 --- /dev/null +++ b/src/SAGisXPlanung/XPlanungPlugin.py @@ -0,0 +1,293 @@ +import logging +import os.path + +from qgis.PyQt import QtWidgets +from qgis.PyQt.QtCore import Qt, pyqtSlot, QObject, QSizeF, QUrl, QDir +from qgis.PyQt.QtGui import QIcon, QPageLayout, QPainter +from qgis.PyQt.QtWidgets import QAction, QMenu, QFileDialog, QStyleOptionGraphicsItem, QToolBar +from qgis.PyQt.QtPrintSupport import QPrinter + +from qgis.core import (QgsMapLayerType, QgsProject, QgsLayerTreeGroup, QgsAnnotationLayer, Qgis, + QgsMapRendererCustomPainterJob, QgsRenderContext, QgsApplication) +from qgis.gui import QgsMapLayerAction +from qgis import processing + +import qasync + +from SAGisXPlanung.BuildingTemplateItem import BuildingTemplateItem +from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry +from SAGisXPlanung.Settings import Settings, is_valid_db, tryConnect +from SAGisXPlanung.gui.XPEditPreFilledObjects import XPEditPreFilledObjectsDialog +from SAGisXPlanung.gui.XPPlanDetailsDialog import displayPlanOnCanvas, reloadPlan +from SAGisXPlanung.gui.XPlanungDialog import XPlanungDialog +from SAGisXPlanung.processing.provider import SAGisProvider +from SAGisXPlanung.utils import createXPlanungIndicators, full_version_required_warning + +logger = logging.getLogger(__name__) + + +class XPlanung(QObject): + def __init__(self, iface): + super(XPlanung, self).__init__() + self.iface = iface + # initialize plugin directory + self.plugin_dir = os.path.dirname(__file__) + + # Declare instance attributes + self.main_action = None + self.data_action = None + self.snapshot_action = None + self.processing_menu = None + self.menu_name = 'SAGis XPlanung' + self.tool = None + self.provider = None + + # Check if plugin was started the first time in current QGIS session + # Must be set in initGui() to survive plugin reloads + self.first_start = None + + self.sagis_menu = self.iface.mainWindow().findChild(QMenu, 'sagis_menu') + if not self.sagis_menu: + self.sagis_menu = QMenu('&SAGis', self.iface.mainWindow().menuBar()) + self.sagis_menu.setObjectName('sagis_menu') + actions = self.iface.mainWindow().menuBar().actions() + self.iface.mainWindow().menuBar().insertMenu(actions[-2], self.sagis_menu) + + self.sagis_toolbar = self.iface.mainWindow().findChild(QToolBar, 'sagis_toolbar') + if not self.sagis_toolbar: + self.sagis_toolbar = QToolBar('SAGis Toolbar', self.iface.mainWindow().menuBar()) + self.sagis_toolbar.setToolTip('SAGis Tools') + self.sagis_toolbar.setObjectName('sagis_toolbar') + self.iface.addToolBar(self.sagis_toolbar) + + self.menu = self.sagis_menu.addMenu(self.menu_name) + self.menu.aboutToShow.connect(self.onXPlanungMenuAboutToShow) + + self.dockWidget = XPlanungDialog(parent=iface.mainWindow()) + self.dockWidget.setMaximumWidth(1000) + self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dockWidget.details_dialog) + self.iface.addTabifiedDockWidget(Qt.RightDockWidgetArea, self.dockWidget, ['xplanung-details'], raiseTab=True) + self.dockWidget.hide() + self.dockWidget.details_dialog.hide() + + self.settings = Settings() + + # TODO: hackish solution, because there currently is no signal on project loaded: + # see https://github.com/qgis/QGIS/issues/40483 + QgsProject.instance().homePathChanged.connect(self.onProjectLoaded) + + self.iface.layerTreeView().layerTreeModel().rowsInserted.connect(self.onRowsInserted) + + def initProcessing(self): + self.menu.addSeparator() + self.processing_menu = self.menu.addMenu('Verarbeitungswerkzeuge') + + self.provider = SAGisProvider() + self.provider.algorithmsLoaded.connect(self.onProcessingAlgorithmsLoaded) + + QgsApplication.processingRegistry().addProvider(self.provider) + + def initGui(self): + """Create the menu entries and toolbar icons inside the QGIS GUI.""" + + xp_icon = os.path.abspath(os.path.join(os.path.dirname(__file__), 'gui/resources/sagis_icon.png')) + settings_icon = ':/images/themes/default/mActionOptions.svg' + self.main_action = QAction(QIcon(xp_icon), 'XPlanung', self.iface.mainWindow()) + self.main_action.setToolTip('Plugin zum Erfassen von XPlanung konformen Bauleitplänen') + self.main_action.triggered.connect(self.run) + self.sagis_toolbar.addAction(self.main_action) + + self.menu.addAction(self.main_action) + + settings_action = QAction(QIcon(settings_icon), 'Einstellungen', self.iface.mainWindow()) + settings_action.setToolTip('SAGis XPlanung konfigurieren') + settings_action.triggered.connect(self.showSettings) + self.menu.addAction(settings_action) + + self.menu.addSeparator() + + self.data_action = QAction('Allgemeine Daten bearbeiten...', self.iface.mainWindow()) + self.data_action.triggered.connect(self.showEditPreFilledObjects) + self.menu.addAction(self.data_action) + + self.snapshot_action = QAction('Aktuellen Kartenauschnitt als PDF exportieren...', self.iface.mainWindow()) + self.snapshot_action.triggered.connect(self.onSnapshotActionTriggered) + self.menu.addAction(self.snapshot_action) + + self.add_config_content_action() + QtWidgets.QApplication.restoreOverrideCursor() + + self.initProcessing() + + # will be set False in run() + self.first_start = True + + def unload(self): + """Removes the plugin menu item, icons and dialogs from QGIS GUI.""" + self.sagis_toolbar.removeAction(self.main_action) + self.menu.deleteLater() + if self.sagis_menu.isEmpty(): + self.sagis_menu.deleteLater() + + MapLayerRegistry().unload() + + self.tool.identifyMenu().removeCustomActions() + QgsProject.instance().homePathChanged.disconnect(self.onProjectLoaded) + self.iface.layerTreeView().layerTreeModel().rowsInserted.disconnect(self.onRowsInserted) + + self.iface.removeDockWidget(self.dockWidget.details_dialog) + self.iface.removeDockWidget(self.dockWidget) + self.dockWidget.details_dialog.deleteLater() + self.dockWidget.deleteLater() + + # unload processing + QgsApplication.processingRegistry().removeProvider(self.provider) + + @pyqtSlot() + def run(self): + if is_valid_db(): + if self.first_start: + self.dockWidget.cbPlaene.refresh() + self.dockWidget.show() + + # Only create GUI ONCE in callback, so that it will only load when the plugin is started + if self.first_start: + self.first_start = False + + @pyqtSlot() + def showSettings(self): + self.settings.exec_() + + self.dockWidget.details_dialog.hide() + if not is_valid_db(): + self.dockWidget.hide() + + self.dockWidget.cbPlaene.refresh() + + @pyqtSlot(bool) + def showEditPreFilledObjects(self, checked): + d = XPEditPreFilledObjectsDialog(parent=self.iface.mainWindow()) + d.exec() + + @qasync.asyncSlot() + async def onXPlanungMenuAboutToShow(self): + try: + tryConnect() + self.data_action.setDisabled(False) + except Exception: + self.data_action.setDisabled(True) + + @qasync.asyncSlot() + async def onProcessingAlgorithmsLoaded(self): + for alg in self.provider.algorithms(): + a = QAction(QIcon(':/images/themes/default/processingAlgorithm.svg'), alg.displayName(), self.processing_menu) + a.triggered.connect(lambda checked, alg_id=alg.id(): processing.execAlgorithmDialog(alg_id, {})) + self.processing_menu.addAction(a) + + self.provider.algorithmsLoaded.disconnect(self.onProcessingAlgorithmsLoaded) + + @qasync.asyncSlot(bool) + async def onSnapshotActionTriggered(self, checked): + filename = QFileDialog.getSaveFileName(self.iface.mainWindow(), + 'Speicherort auswählen', directory=f'export.pdf', filter=f'*.pdf') + + settings = self.iface.mapCanvas().mapSettings() + settings.setFlag(Qgis.MapSettingsFlag.Antialiasing, True) + settings.setFlag(Qgis.MapSettingsFlag.ForceVectorOutput, True) + + printer = QPrinter(QPrinter.HighResolution) + printer.setOutputFileName(filename[0]) + printer.setOutputFormat(QPrinter.PdfFormat) + + printer.setPageOrientation(QPageLayout.Orientation.Portrait) + outputSize = settings.outputSize() + printer.setPaperSize(QSizeF(outputSize * 25.4 / settings.outputDpi()), QPrinter.Millimeter) + printer.setPageMargins(0, 0, 0, 0, QPrinter.Millimeter) + printer.setResolution(settings.outputDpi()) + + dest_painter = QPainter(printer) + + render_job = QgsMapRendererCustomPainterJob(settings, dest_painter) + render_job.prepare() + render_job.renderPrepared() + + context = QgsRenderContext.fromMapSettings(settings) + context.setPainter(dest_painter) + + option = QStyleOptionGraphicsItem() + option.initFrom(self.iface.mapCanvas()) + + dest_painter.begin(printer) + + for canvas_item in self.iface.mapCanvas().items(): + if isinstance(canvas_item, BuildingTemplateItem): + dest_painter.save() + itemScenePos = canvas_item.scenePos() + dest_painter.translate(itemScenePos.x(), itemScenePos.y()) + canvas_item.paint(dest_painter, option) + dest_painter.restore() + + dest_painter.end() + + del dest_painter + del printer + + url = QUrl.fromLocalFile(filename[0]) + path = QDir.toNativeSeparators(filename[0]) + self.iface.messageBar().pushMessage("Als PDF Speichern", + f"Kartenausschnitt erfolgreich unter {path} gespeichert", + level=Qgis.Success) + + def add_config_content_action(self): + """Add the new action to the identify menu""" + + xp_icon = os.path.abspath(os.path.join(os.path.dirname(__file__), 'gui/resources/xplanung_icon.png')) + action = [a for a in self.iface.attributesToolBar().actions() if a.objectName() == 'mActionIdentify'][0] + # action.triggered.disconnect() + + action.trigger() + self.tool = self.iface.mapCanvas().mapTool() + + menu = self.tool.identifyMenu() + xp_action = QgsMapLayerAction("Als Planinhalt konfigurieren", menu, QgsMapLayerType.VectorLayer, + QgsMapLayerAction.SingleFeature, QIcon(xp_icon)) + xp_action.triggeredForFeature.connect(self.onActionTriggered) + menu.addCustomAction(xp_action) + + self.iface.actionPan().trigger() + + def onActionTriggered(self, layer, feat): + full_version_required_warning() + + def onProjectLoaded(self): + logger.debug('project loaded') + layers = QgsProject.instance().layerTreeRoot().findGroups(recursive=True) + for group in layers: + if not isinstance(group, QgsLayerTreeGroup) or 'xplanung_id' not in group.customProperties(): + continue + + try: + for tree_layer in group.findLayers(): + if isinstance(tree_layer.layer(), QgsAnnotationLayer): + QgsProject().instance().removeMapLayer(tree_layer.layer()) + continue + MapLayerRegistry().addLayer(tree_layer.layer(), add_to_legend=False) + displayPlanOnCanvas(group.customProperty('xplanung_id'), layer_group=group) + except Exception as e: + logger.exception(e) + + def onRowsInserted(self, parent, first, last): + model = self.iface.layerTreeView().layerTreeModel() + index = model.index(first, 0, parent) + node = model.index2node(index) + + if not isinstance(node, QgsLayerTreeGroup) or 'xplanung_id' not in node.customProperties(): + return + + if not self.iface.layerTreeView().indicators(node): + xp_indicator, reload_indicator = createXPlanungIndicators() + plan_xid = node.customProperty('xplanung_id') + reload_indicator.clicked.connect(lambda i, p=plan_xid: reloadPlan(p)) + + self.iface.layerTreeView().addIndicator(node, xp_indicator) + self.iface.layerTreeView().addIndicator(node, reload_indicator) diff --git a/src/SAGisXPlanung/__init__.py b/src/SAGisXPlanung/__init__.py new file mode 100644 index 0000000..f8f241a --- /dev/null +++ b/src/SAGisXPlanung/__init__.py @@ -0,0 +1,189 @@ +import importlib +import json +import logging +import os.path +import os +import platform +import sys +import asyncio +from enum import Enum +from io import StringIO + +from qgis.PyQt.QtCore import QCoreApplication +from qgis.core import Qgis +from qgis.PyQt.uic import compiler + +import SAGisXPlanung + +logger = logging.getLogger(__name__) + + +# ================ LOG UNCAUGHT EXCEPTIONS ==================== +def handle_exception(exc_type, exc_value, exc_traceback): + if issubclass(exc_type, KeyboardInterrupt): + sys.__excepthook__(exc_type, exc_value, exc_traceback) + return + + logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) + + +sys.excepthook = handle_exception + + +# ================ SETUP ASYNCIO EVENT LOOP ==================== +def setup_asyncio(): + try: + from qasync import QEventLoop + + __app = QCoreApplication.instance() + __loop = QEventLoop(__app, already_running=True) + asyncio.set_event_loop(__loop) + except Exception as e: + logger.error(e) + + +# ================ COMPILE UI FILES ====================== + +# workaround for compiling ui files with uic which have a custom widget class as top level widget +# uic assumes that all widgets reside within QtWidgets namespace which is not true for custom widgets +# this workaround simply removes the call to `getattr(QtWidgets, winfo["baseclass"])` +# all base classes must therefore be specified manually when loading ui form classes with this function +# +# some comments on the issue by qgis devs here: +# https://lists.osgeo.org/pipermail/qgis-developer/2018-January/051342.html +def compile_ui_file(ui_file): + code_string = StringIO() + winfo = compiler.UICompiler().compileUi(ui_file, code_string, False, '_rc', '.') + + ui_globals = {} + exec(code_string.getvalue(), ui_globals) + return ui_globals[winfo["uiclass"]] + + +# ================ PYTHON DEBUG CONFIG ==================== +try: + sys.path.append( + "C:/Users/Jakob/AppData/Local/JetBrains/Toolbox/apps/PyCharm-P/ch-0/231.9011.38/debug-eggs/pydevd-pycharm.egg") + import pydevd_pycharm + + pydevd_pycharm.settrace('localhost', port=51699, stdoutToServer=True, stderrToServer=True, suspend=False) +except Exception as e: + logger.info('Python debug config failed') + logger.error(e) + +# ========================================================= + +VERSION = '2.2.0' +COMPATIBLE_DB_REVISIONS = ['ce95b86bc010'] +DEPENDENCIES = [ + 'lxml', + 'SQLAlchemy==1.4.13', + 'GeoAlchemy2==0.12.5', + 'shapely==2.0.0', + 'qasync==0.22.0', + 'asyncpg==0.26.0' +] + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +Base = None +Session = None +SessionAsync = None + + +def setup_sqlalchemy(): + try: + from sqlalchemy.ext.asyncio import AsyncSession + from sqlalchemy.orm import declarative_base, sessionmaker + + global Base + global Session + global SessionAsync + Base = declarative_base() + Session = sessionmaker() + SessionAsync = sessionmaker(expire_on_commit=False, class_=AsyncSession) + except Exception as e: + logger.error(e) + + +if os.environ.get('CI'): + setup_sqlalchemy() + + +class XPlanVersion(Enum): + FIVE_THREE = '5.3' + SIX = '6.0' + + @classmethod + def from_namespace(cls, ns: str): + if ns == "http://www.xplanung.de/xplangml/6/0": + return cls.SIX + elif ns.startswith("http://www.xplanung.de/xplangml/5/"): + return cls.FIVE_THREE + raise ValueError(f'XPlanung Version {ns} nicht unterstüzt.') + + def short_id(self) -> str: + return str(self.value).replace('.', '') + + +def system_info(): + try: + info = { + 'platform': platform.system(), + 'platform-release': platform.release(), + 'platform-version': platform.version(), + 'architecture': platform.machine(), + 'processor': platform.processor(), + } + return json.dumps(info) + except Exception as ex: + logging.exception(ex) + + +def qgis_info(): + try: + qgs_info = { + 'xplanung_version': VERSION, + 'qgis_version': Qgis.version(), + 'geos_version': Qgis.geosVersion(), + } + return json.dumps(qgs_info) + except Exception as ex: + logging.exception(ex) + + +def classFactory(iface): + """Load XPlanung class from file XPlanung. + + :param iface: A QGIS interface instance. + :type iface: QgsInterface + """ + formatter = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + logfilename = os.path.join(BASE_DIR, 'XPlanung.log') + logging.basicConfig(filename=logfilename, level=logging.DEBUG, format=formatter, datefmt='%m.%d.%Y %H:%M:%S', + force=True) + + # logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) + logging.getLogger('qasync').setLevel(logging.ERROR) + logging.getLogger('PyQt5.uic.uiparser').setLevel(logging.ERROR) + logging.getLogger('PyQt5.uic.properties').setLevel(logging.ERROR) + + logger.info(system_info()) + logger.info(qgis_info()) + + from SAGisXPlanung.config.dependencies import check + re = check(DEPENDENCIES) + if not re: + # create anonymous object so that qgis can call initGui and unload without crashing + return type('', (object,), {"initGui": (lambda self: None), "unload": (lambda self: None)})() + + setup_asyncio() + setup_sqlalchemy() + # reload config module required so that new configured `Base` is used + importlib.reload(SAGisXPlanung.config) + + from SAGisXPlanung.Settings import configureSession + configureSession() + + from SAGisXPlanung.XPlanungPlugin import XPlanung + return XPlanung(iface) diff --git a/src/SAGisXPlanung/config/__init__.py b/src/SAGisXPlanung/config/__init__.py new file mode 100644 index 0000000..eed1b64 --- /dev/null +++ b/src/SAGisXPlanung/config/__init__.py @@ -0,0 +1,64 @@ +import html +import logging +import yaml +from typing import Union +from pathlib import Path + +from SAGisXPlanung import XPlanVersion, Base + +from qgis.PyQt.QtCore import QSettings + +try: + from functools import cache +except ImportError: + from functools import lru_cache as cache + + +logger = logging.getLogger(__name__) + + +SVG_CONFIG = yaml.safe_load(Path(__file__).with_name('svg_config.yaml').read_text()) + + +def table_name_to_class(table_name: str) -> type: + for mapper in Base.registry.mappers: + if mapper.class_.__tablename__ == table_name: + return mapper.class_ + + +@cache +def docstring_config(version: XPlanVersion): + return yaml.safe_load(Path(__file__).with_name(f'doc_string_config_{version.short_id()}.yaml').read_text()) + + +def xplan_tooltip(xtype: type, attribute_name: str, plain=False) -> Union[None, str]: + """ + Returns XPlanung description as rich-text html div, which can be used for tooltips for given class and attribute. + Returns None if no description could be found. + """ + try: + _table = getattr(xtype, attribute_name).property.columns[0].table + _class = table_name_to_class(_table.name) + if not _class: + return "" + config = docstring_config(export_version()) + tooltip = config[_class.__name__]['attributes'][attribute_name]['doc'] + + if plain: + return tooltip + + # hackish solution for https://bugreports.qt.io/browse/QTBUG-41051 + # Convert plaintext tooltip into a rich text tooltip which allows word-wrap and doesnt grow to infinite width: + # * Escape all possible HTML syntax + # * Embed tooltip in weird "..." tag. + return '{}'.format(html.escape(tooltip)) + except (KeyError, AttributeError) as e: + logger.warning(f'Could not find tooltip in config: {e} for super class {xtype.__name__}') + + +def export_version(): + qs = QSettings() + version = qs.value(f"plugins/xplanung/export_version", None) + if version is None: + return XPlanVersion.FIVE_THREE + return XPlanVersion(version) diff --git a/src/SAGisXPlanung/config/dependencies.py b/src/SAGisXPlanung/config/dependencies.py new file mode 100644 index 0000000..23a9156 --- /dev/null +++ b/src/SAGisXPlanung/config/dependencies.py @@ -0,0 +1,52 @@ +import importlib + +from qgis.core import Qgis +from qgis.PyQt.QtWidgets import QMessageBox, QLabel +from qgis.utils import iface + + +def check(required_packages): + """ Checks if required packages are correctly installed. """ + missing_packages = [] + for package in required_packages: + try: + importlib.import_module(str(package.partition('==')[0]).lower()) + except ImportError: + missing_packages.append(package) + + if not missing_packages: + return True + + message = "Die folgenden Softwarekomponenten werden zur Ausführung von SAGis XPlanung benötigt:\n\n" + message += "\n".join(missing_packages) + message += "\n\nSollen die fehlenden Komponenten installliert werden?" + + dialog = QMessageBox(QMessageBox.Question, 'Fehlende Abhängigkeiten', message, QMessageBox.Yes | QMessageBox.No) + reply = dialog.exec() + + if reply == QMessageBox.No: + return False + + error = False + log = [] + for package in missing_packages: + try: + import subprocess + ret = subprocess.call(['python3', '-m', 'pip', 'install', package]) + if ret == 0: + log.append(f'{package} ... installiert') + else: + raise Exception('Konnte nicht installiert werden.') + except: + error = True + log.append(f'{package} ... Fehler bei Installation') + + if error: + iface.messageBar().pushMessage("XPlanung", + f'Fehler beim Installieren der Python-Pakete', '\n'.join(log), + level=Qgis.Critical) + else: + iface.messageBar().pushMessage("XPlanung", + f'Python-Pakete erfolgreich installiert', '\n'.join(log), + level=Qgis.Success) + return True diff --git a/src/SAGisXPlanung/config/doc_string_config_53.yaml b/src/SAGisXPlanung/config/doc_string_config_53.yaml new file mode 100644 index 0000000..2e8297a --- /dev/null +++ b/src/SAGisXPlanung/config/doc_string_config_53.yaml @@ -0,0 +1,3920 @@ +BP_AbgrabungsFlaeche: + attributes: + abbaugut: + doc: Bezeichnung des Abbauguts. + doc: 'Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen + (§9, Abs. 1, Nr. 17 BauGB)). Hier: Flächen für Abgrabungen und die Gewinnung von + Bodenschätzen.' +BP_AbstandsFlaeche: + attributes: + tiefe: + doc: Absolute Angabe der Tiefe. + doc: "Festsetzung eines vom Bauordnungsrecht abweichenden Maßes der Tiefe der Abstandsfläche\ + \ gemäß § 9 Abs 1. Nr. 2a BauGB\r\n" +BP_AbstandsMass: + attributes: + endWinkel: + doc: Endwinkel für die Planarstellung des Abstandsmaßes (nur relevant für Maßkreise). + Die Winkelwerte beziehen sich auf den Rechtswert (Ost-Richtung) + startWinkel: + doc: Startwinkel für die Plandarstellung des Abstandsmaßes (nur relevant für + Maßkreise). Die Winkelwerte beziehen sich auf den Rechtswert (Ost-Richtung) + typ: + doc: Typ der Massangabe (Maßpfeil oder Maßkreis). + wert: + doc: Wertangabe des Abstandsmaßes. Bei Maßpfeilen (typ == 1000) enthält das + Attribut die Länge des Maßpfeilen (uom = "m"), bei Maßkreisen den von startWinkel + und endWinkel eingeschlossenen Winkel (uom = "grad"). + doc: "Darstellung von Maßpfeilen oder Maßkreisen in BPlänen, um eine eindeutige\ + \ Vermassung einzelner Festsetzungen zu erreichen.\r\n" +BP_AbweichungVonBaugrenze: + attributes: {} + doc: Linienhafte Festlegung des Umfangs der Abweichung von der Baugrenze (§23 Abs. + 3 Satz 3 BauNVO). +BP_AbweichungVonUeberbaubererGrundstuecksFlaeche: + attributes: {} + doc: Flächenhafte Festlegung des Umfangs der Abweichung von der überbaubaren Grundstücksfläche + (§23 Abs. 3 Satz 3 BauNVO). +BP_AnpflanzungBindungErhaltung: + attributes: + anzahl: + doc: Anzahl der anzupflanzenden Objekte + baumArt: + doc: Textliche Spezifikation einer Baumart. + gegenstand: + doc: Gegenstand der Maßnahme. + istAusgleich: + doc: Gibt an, ob die Fläche oder Maßnahme zum Ausgleich von Eingriffen genutzt + wird. + kronendurchmesser: + doc: Durchmesser der Baumkrone bei zu erhaltenden Bäumen. + massnahme: + doc: "Art der Maßnahme\r\n" + mindesthoehe: + doc: Mindesthöhe des Gegenstands der Festsetzung + pflanztiefe: + doc: Pflanztiefe + doc: "Festsetzung des Anpflanzens von Bäumen, Sträuchern und sonstigen Bepflanzungen;\r\ + \n" +BP_AufschuettungsFlaeche: + attributes: + aufschuettungsmaterial: + doc: Bezeichnung des aufgeschütteten Materials + doc: 'Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen + (§ 9 Abs. 1 Nr. 17 und Abs. 6 BauGB). Hier: Flächen für Aufschüttungen' +BP_AusgleichsFlaeche: + attributes: + massnahme: + doc: "Auf der Fläche durchzuführende Maßnahmen.\r\n" + refLandschaftsplan: + doc: Referenz auf den Landschaftsplan. + refMassnahmenText: + doc: Referenz auf ein Dokument, das die durchzuführenden Maßnahmen beschreibt. + sonstZiel: + doc: Textlich formuliertes Ziel, wenn das Attribut ziel den Wert 9999 (Sonstiges) + hat. + ziel: + doc: Ziel der Ausgleichsmaßnahme + doc: Festsetzung einer Fläche zum Ausgleich im Sinne des § 1a Abs.3 und §9 Abs. + 1a BauGB. +BP_AusgleichsMassnahme: + attributes: + massnahme: + doc: Durchzuführende Ausgleichsmaßnahme. + refLandschaftsplan: + doc: Referenz auf den Landschaftsplan. + refMassnahmenText: + doc: Referenz auf ein Dokument, das die durchzuführenden Maßnahmen beschreibt. + sonstZiel: + doc: Textlich formuliertes Ziel, wenn das Attribut ziel den Wert 9999 (Sonstiges) + hat. + ziel: + doc: Ziel der Ausgleichsmaßnahme + doc: Festsetzung einer Einzelmaßnahme zum Ausgleich im Sinne des § 1a Abs.3 und + §9 Abs. 1a BauGB. +BP_BauGrenze: + attributes: + bautiefe: + doc: Angabe einer Bautiefe. + geschossMax: + doc: Gibt bei geschossweiser Festsetzung die Nummer des Geschosses an, bis zu + der die Festsetzung gilt. Wenn das Attribut nicht belegt ist, gilt die Festsetzung + für alle Geschosse ab einschl. "geschossMin". + geschossMin: + doc: Gibt bei geschossweiser Festsetzung die Nummer des Geschosses an, ab den + die Festsetzung gilt. Wenn das Attribut nicht belegt ist, gilt die Festsetzung + für alle Geschosse bis einschl. "geschossMax". + doc: 'Festsetzung einer Baugrenze (§9 Abs. 1 Nr. 2 BauGB, §22 und 23 BauNVO). Über + die Attribute ' +BP_BauLinie: + attributes: + bautiefe: + doc: Angabe einer Bautiefe. + geschossMax: + doc: Gibt bei geschossweiser Feststzung die Nummer des Geschosses an, bis zu + der die Festsetzung gilt. Wenn das Attribut nicht belegt ist, gilt die Festsetzung + für alle Geschosse ab einschl. "geschossMin". + geschossMin: + doc: Gibt bei geschossweiser Festsetzung die Nummer des Geschosses an, ab den + die Festsetzung gilt. Wenn das Attribut nicht belegt ist, gilt die Festsetzung + für alle Geschosse bis einschl. "geschossMax". + doc: 'Festsetzung einer Baulinie (§9 Abs. 1 Nr. 2 BauGB, §22 und 23 BauNVO). Über + die Attribute ' +BP_BaugebietsTeilFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + DN: + doc: "Maximal zulässige Dachneigung.\r\n\r\nDies Attribut ist veraltet und wird\ + \ in Version 6.0 wegfallen. Es sollte stattdessen der Datentyp BP_Dachgestaltung\ + \ (Attribut dachgestaltung) verwendet werden." + DNZwingend: + doc: "Zwingend vorgeschriebene Dachneigung.\r\n\r\nDies Attribut ist veraltet\ + \ und wird in Version 6.0 wegfallen. Es sollte stattdessen der Datentyp BP_Dachgestaltung\ + \ (Attribut dachgestaltung) verwendet werden." + DNmax: + doc: "Maxmal zulässige Dachneigung bei einer Bereichsangabe.\r\n\r\nDies Attribut\ + \ ist veraltet und wird in Version 6.0 wegfallen. Es sollte stattdessen der\ + \ Datentyp BP_Dachgestaltung (Attribut dachgestaltung) verwendet werden." + DNmin: + doc: "Minimal zulässige Dachneigung bei einer Bereichsangabe.\r\n\r\nDies Attribut\ + \ ist veraltet und wird in Version 6.0 wegfallen. Es sollte stattdessen der\ + \ Datentyp BP_Dachgestaltung (Attribut dachgestaltung) verwendet werden." + FR: + doc: Vorgeschriebene Firstrichtung + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFAntGewerbe: + doc: "Festsetzung nach §6a Abs. (4) Nr. 4 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass \r\nin Gebäuden ein im Bebauungsplan\ + \ bestimmter Anteil der zulässigen \r\nGeschossfläche für gewerbliche Nutzungen\ + \ zu verwenden ist." + GFAntWohnen: + doc: "Festsetzung nach §4a Abs. (4) Nr. 2 bzw. §6a Abs. (4) Nr. 3 BauNVO: Für\ + \ besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann\ + \ festgesetzt werden, dass \r\nin Gebäuden ein im Bebauungsplan bestimmter\ + \ Anteil der zulässigen \r\nGeschossfläche für Wohnungen zu verwenden ist." + GFGewerbe: + doc: "Festsetzung nach §6a Abs. (4) Nr. 4 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass \r\nin Gebäuden eine im Bebauungsplan\ + \ bestimmte Größe der Geschossfläche für gewerbliche Nutzungen zu verwenden\ + \ ist." + GFWohnen: + doc: "Festsetzung nach §4a Abs. (4) Nr. 2 bzw. §6a Abs. (4) Nr. 3 BauNVO: Für\ + \ besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann\ + \ festgesetzt werden, dass \r\nin Gebäuden eine im Bebauungsplan bestimmte\ + \ Größe der Geschossfläche für Wohnungen zu verwenden ist." + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + VF: + doc: Festsetzung der maximal zulässigen Verkaufsfläche in einem Sondergebiet + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + ZWohn: + doc: 'Festsetzung nach §4a Abs. (4) Nr. 1 bzw. nach §6a Abs. (4) Nr. 2 BauNVO: + Für besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann + festgesetzt werden, dass in Gebäuden oberhalb eines im Bebauungsplan bestimmten + Geschosses nur Wohnungen zulässig sind.' + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximalzahl von oberirdischen zurückgesetzten Vollgeschossen als Staffelgeschoss.. + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + abweichendeBauweise: + doc: Nähere Bezeichnung einer "Abweichenden Bauweise" ("bauweise" == 3000). + abweichungBauNVO: + doc: Art der zulässigen Abweichung von der BauNVO. + abweichungText: + doc: Texliche Beschreibung der abweichenden Bauweise + allgArtDerBaulNutzung: + doc: "Spezifikation der allgemeinen Art der baulichen N utzung.\r\nDies Attribut\ + \ ist als \"veraltet\" gekennzeichnet und wird in Version 6.0 evtl. wegfallen." + bauweise: + doc: Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB). + bebauungRueckwaertigeGrenze: + doc: Festsetzung der Bebauung der rückwärtigen Grundstücksgrenze (§9, Abs. 1, + Nr. 2 BauGB). + bebauungSeitlicheGrenze: + doc: Festsetzung der Bebauung der seitlichen Grundstücksgrenze (§9, Abs. 1, + Nr. 2 BauGB). + bebauungVordereGrenze: + doc: Festsetzung der Bebauung der vorderen Grundstücksgrenze (§9, Abs. 1, Nr. + 2 BauGB). + bebauungsArt: + doc: Detaillierte Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB). + besondereArtDerBaulNutzung: + doc: Festsetzung der Art der baulichen Nutzung (§9, Abs. 1, Nr. 1 BauGB). + dachform: + doc: "Erlaubte Dachformen.\r\n\r\nDies Attribut ist veraltet und wird in Version\ + \ 6.0 wegfallen. Es sollte stattdessen der Datentyp BP_Dachgestaltung (Attribut\ + \ dachgestaltung) verwendet werden." + dachgestaltung: + doc: Parameter zur Einschränkung der zulässigen Dachformen. + detaillierteArtDerBaulNutzung: + doc: Über eine Codeliste definierte detailliertere Nutzungsart. + detaillierteDachform: + doc: "Über eine Codeliste definiertere detailliertere Dachform.\r\nDer an einer\ + \ bestimmten Listenposition aufgeführte Wert von \"detaillierteDachform\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von dachform.\r\ + \n\r\nDies Attribut ist veraltet und wird in Version 6.0 wegfallen. Es sollte\ + \ stattdessen der Datentyp BP_Dachgestaltung (Attribut dachgestaltung) verwendet\ + \ werden." + detaillierteSondernutzung: + doc: Über eine Codeliste definierte detailliertere Sondernutzungsart. + nutzungText: + doc: 'Bei Nutzungsform "Sondergebiet" ("besondereArtDerBaulNutzung" == 4000): + Kurzform der besonderen Art der baulichen Nutzung.' + refGebaeudequerschnitt: + doc: "Referenz auf ein Dokument mit vorgeschriebenen Gebäudequerschnitten.\r\ + \n" + sondernutzung: + doc: Differenziert Sondernutzungen nach §10 und §11 der BauNVO von 1977 und + 1990. Das Attribut wird nur benutzt, wenn besondereArtDerBaulNutzung unbelegt + ist oder einen der Werte 2000 bzw. 2100 hat. + vertikaleDifferenzierung: + doc: Gibt an, ob eine vertikale Differenzierung der Gebäude vorgeschrieben ist. + wohnnutzungEGStrasse: + doc: "Festsetzung nach §6a Abs. (4) Nr. 1 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass in Gebäuden \r\nim Erdgeschoss\ + \ an der Straßenseite eine Wohnnutzung nicht oder nur ausnahmsweise zulässig\ + \ ist." + zugunstenVon: + doc: Angabe des Begünstigten einer Ausweisung. + doc: 'Teil eines Baugebiets mit einheitlicher Art der baulichen Nutzung. Das Maß + der baulichen Nutzung sowie Festsetzungen zur Bauweise oder Grenzbebauung können + innerhalb einer ' +BP_Bereich: + attributes: + gehoertZuPlan: + doc: Referenz eines Bereichs eines Bebauungsplans auf das zugehörige Plan-Objekt. + versionBauGBDatum: + doc: "Bekanntmachungs-Datum der zugrunde liegenden Version des BauGB.\r\n\r\n\ + Das Attribut ist veraltet und wird in XPlanGML 6.0 wegfallen. Es sollte stattdessen\ + \ das gleichnamige Attribut von BP_Plan verwendet werden." + versionBauGBText: + doc: "Textliche Spezifikation der zugrunde liegenden Version des BauGB.\r\n\r\ + \nDas Attribut ist veraltet und wird in XPlanGML 6.0 wegfallen. Es sollte\ + \ stattdessen das gleichnamige Attribut von BP_Plan verwendet werden." + versionBauNVODatum: + doc: "Bekanntmachungs-Datum der zugrunde liegenden Version der BauNVO.\r\n\r\ + \nDas Attribut ist veraltet und wird in XPlanGML 6.0 wegfallen. Es sollte\ + \ stattdessen das gleichnamige Attribut von BP_Plan verwendet werden." + versionBauNVOText: + doc: "Textliche Spezifikation der zugrunde liegenden Version der BauNVO.\r\n\ + \r\nDas Attribut ist veraltet und wird in XPlanGML 6.0 wegfallen. Es sollte\ + \ stattdessen das gleichnamige Attribut von BP_Plan verwendet werden." + versionSonstRechtsgrundlageDatum: + doc: "Bekanntmachungs-Datum einer zugrunde liegenden anderen Rechtsgrundlage\ + \ als BauGB / BauNVO.\r\n\r\nDas Attribut ist veraltet und wird in XPlanGML\ + \ 6.0 wegfallen. Es sollte stattdessen das gleichnamige Attribut von BP_Plan\ + \ verwendet werden." + versionSonstRechtsgrundlageText: + doc: "Textliche Spezifikation einer zugrunde liegenden anderen Rechtsgrundlage\ + \ als BauGB / BauNVO.\r\n\r\nDas Attribut ist veraltet und wird in XPlanGML\ + \ 6.0 wegfallen. Es sollte stattdessen das gleichnamige Attribut von BP_Plan\ + \ verwendet werden." + doc: Diese Klasse modelliert einen Bereich eines Bebauungsplans, z.B. einen räumlichen + oder sachlichen Teilbereich. +BP_BereichOhneEinAusfahrtLinie: + attributes: + typ: + doc: Typ des Bereiches ohne Ein- und Ausfahrt + doc: "Bereich ohne Ein- und Ausfahrt (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB).\r\n" +BP_BesondererNutzungszweckFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + DN: + doc: "Maximal zulässige Dachneigung.\r\n\r\nDies Attribut ist veraltet und wird\ + \ in Version 6.0 wegfallen. Es sollte stattdessen der Datentyp BP_Dachgestaltung\ + \ (Attribut dachgestaltung) verwendet werden." + DNZwingend: + doc: "Zwingend vorgeschriebene Dachneigung.\r\n\r\nDies Attribut ist veraltet\ + \ und wird in Version 6.0 wegfallen. Es sollte stattdessen der Datentyp BP_Dachgestaltung\ + \ (Attribut dachgestaltung) verwendet werden." + DNmax: + doc: "Maxmal zulässige Dachneigung bei einer Bereichsangabe.\r\n\r\nDies Attribut\ + \ ist veraltet und wird in Version 6.0 wegfallen. Es sollte stattdessen der\ + \ Datentyp BP_Dachgestaltung (Attribut dachgestaltung) verwendet werden." + DNmin: + doc: "Minimal zulässige Dachneigung bei einer Bereichsangabe.\r\n\r\nDies Attribut\ + \ ist veraltet und wird in Version 6.0 wegfallen. Es sollte stattdessen der\ + \ Datentyp BP_Dachgestaltung (Attribut dachgestaltung) verwendet werden." + FR: + doc: Vorgeschriebene Firstrichtung + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximalzahl von oberirdischen zurückgesetzten Vollgeschossen als Staffelgeschoss.. + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + abweichendeBauweise: + doc: Nähere Bezeichnung einer "Abweichenden Bauweise" ("bauweise" == 3000). + bauweise: + doc: Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB). + bebauungsArt: + doc: Detaillierte Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB). + dachform: + doc: "Erlaubte Dachformen.\r\n\r\nDies Attribut ist veraltet und wird in Version\ + \ 6.0 wegfallen. Es sollte stattdessen der Datentyp BP_Dachgestaltung (Attribut\ + \ dachgestaltung) verwendet werden." + dachgestaltung: + doc: Parameter zur Einschränkung der zulässigen Dachformen. + detaillierteDachform: + doc: "Über eine Codeliste definiertere detailliertere Dachform.\r\nDer an einer\ + \ bestimmten Listenposition aufgeführte Wert von \"detaillierteDachform\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von dachform.\r\ + \n\r\nDies Attribut ist veraltet und wird in Version 6.0 wegfallen. Es sollte\ + \ stattdessen der Datentyp BP_Dachgestaltung (Attribut dachgestaltung) verwendet\ + \ werden." + zweckbestimmung: + doc: Angabe des besonderen Nutzungszwecks. + doc: "Festsetzung einer Fläche mit besonderem Nutzungszweck, der durch besondere\ + \ städtebauliche Gründe erfordert wird (§9 Abs. 1 Nr. 9 BauGB.)\r\n" +BP_BodenschaetzeFlaeche: + attributes: + abbaugut: + doc: Bezeichnung des Abbauguts. + doc: "Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen\ + \ (§ 9 Abs. 1 Nr. 17 und Abs. 6 BauGB). Hier: Flächen für Gewinnung von Bodenschätzen\r\ + \n" +BP_Dachgestaltung: + attributes: + DN: + doc: Maximal zulässige Dachneigung + DNmax: + doc: Maximale Dachneigung bei einer Bereichsangabe. Das Attribut DNmin muss + ebenfalls belegt sein. + DNmin: + doc: Minimale Dachneigung bei einer Bereichsangabe. Das Attribut DNmax muss + ebenfalls belegt sein. + DNzwingend: + doc: Zwingend vorgeschriebene Dachneigung + dachform: + doc: Erlaubte Dachform + detaillierteDachform: + doc: 'Über eine Codeliste definiertere detailliertere Dachform. ' + doc: Zusammenfassung von Parametern zur Festlegung der zulässigen Dachformen. +BP_EinfahrtPunkt: + attributes: + typ: + doc: Typ der Einfahrt + doc: Punktförmig abgebildete Einfahrt (§9 Abs. 1 Nr. 11 und Abs. 6 BauGB). +BP_EinfahrtsbereichLinie: + attributes: + typ: + doc: Typ der Einfahrt + doc: "Linienhaft modellierter Einfahrtsbereich (§9 Abs. 1 Nr. 11 und Abs. 6 BauGB).\r\ + \n" +BP_EingriffsBereich: + attributes: {} + doc: "Bestimmt einen Bereich, in dem ein Eingriff nach dem Naturschutzrecht zugelassen\ + \ wird, der durch geeignete Flächen oder Maßnahmen ausgeglichen werden muss.\r\ + \n" +BP_EmissionskontingentLaerm: + attributes: + ekwertNacht: + doc: Emissionskontingent Nacht in db + ekwertTag: + doc: Emissionskontingent Tag in db + erlaeuterung: + doc: Erläuterung + doc: Lärmemissionskontingent eines Teilgebietes nach DIN 45691, Abschnitt 4.6 +BP_EmissionskontingentLaermGebiet: + attributes: + gebietsbezeichnung: + doc: Bezeichnung des Immissionsgebietes + doc: Lärmemissionskontingent eines Teilgebietes, das einem bestimmten Immissionsgebiet + außerhalb des Geltungsbereiches des BPlans zugeordnet ist (Anhang A4 von DIN 45691). +BP_ErhaltungsBereichFlaeche: + attributes: + grund: + doc: Erhaltungsgrund + doc: "Fläche, auf denen der Rückbau, die Änderung oder die Nutzungsänderung baulichen\ + \ Anlagen der Genehmigung durch die Gemeinde bedarf (§172 BauGB)\r\n" +BP_FestsetzungNachLandesrecht: + attributes: + kurzbeschreibung: + doc: Kurzbeschreibung der Festsetzung + doc: Festsetzung nach § 9 Nr. (4) BauGB. +BP_FirstRichtungsLinie: + attributes: {} + doc: "Gestaltungs-Festsetzung der Firstrichtung, beruhend auf Landesrecht, gemäß\ + \ §9 Abs. 4 BauGB.\r\n" +BP_FlaecheOhneFestsetzung: + attributes: {} + doc: Fläche, für die keine geplante Nutzung angegeben werden kann +BP_Flaechenobjekt: + attributes: + flaechenschluss: + doc: 'Zeigt an, ob das Objekt als Flächenschlussobjekt oder Überlagerungsobjekt + gebildet werden soll. Flächenschlussobjekte dürfen sich nicht überlappen, + sondern nur an den Flächenrändern berühren, wobei die jeweiligen Stützpunkte + der Randkurven übereinander liegen müssen. Die Vereinigung der Flächenschlussobjekte + überdeckt den Geltungsbereich des Bebauungsplans vollständig. ' + position: + doc: 'Flächenhafter Raumbezug des Objektes (Eine Einzelfläche oder eine Menge + von Flächen, die sich nicht überlappen dürfen). ' + doc: "Basisklasse für alle Objekte eines Bebauungsplans mit flächenhaftem Raumbezug.\ + \ Die von BP_Flaechenobjekt abgeleiteten Fachobjekte können sowohl als Flächenschlussobjekte\ + \ als auch als Überlagerungsobjekte auftreten.\r\n" +BP_Flaechenschlussobjekt: + attributes: {} + doc: "Basisklasse für alle Objekte eines Bebauungsplans mit flächenhaftem Raumbezug,\ + \ die auf Ebene 0 immer Flächenschlussobjekte sind.\r\n" +BP_FoerderungsFlaeche: + attributes: {} + doc: "Fläche, auf der ganz oder teilweise nur Wohngebäude, die mit Mitteln der sozialen\ + \ Wohnraumförderung gefördert werden könnten, errichtet werden dürfen (§9, Abs.\ + \ 1, Nr. 7 BauGB).\r\n" +BP_FreiFlaeche: + attributes: + nutzung: + doc: Festgesetzte Nutzung der Freifläche. + doc: "Umgrenzung der Flächen, die von der Bebauung freizuhalten sind, und ihre Nutzung\ + \ (§ 9 Abs. 1 Nr. 10 BauGB).\r\n" +BP_GebaeudeFlaeche: + attributes: {} + doc: "Grundrissfläche eines existierenden Gebäudes\r\n" +BP_GemeinbedarfsFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + DN: + doc: "Maximal zulässige Dachneigung.\r\n\r\nDies Attribut ist veraltet und wird\ + \ in Version 6.0 wegfallen. Es sollte stattdessen der Datentyp BP_Dachgestaltung\ + \ (Attribut dachgestaltung) verwendet werden." + DNZwingend: + doc: "Zwingend vorgeschriebene Dachneigung.\r\n\r\nDies Attribut ist veraltet\ + \ und wird in Version 6.0 wegfallen. Es sollte stattdessen der Datentyp BP_Dachgestaltung\ + \ (Attribut dachgestaltung) verwendet werden." + DNmax: + doc: "Maxmal zulässige Dachneigung bei einer Bereichsangabe.\r\n\r\nDies Attribut\ + \ ist veraltet und wird in Version 6.0 wegfallen. Es sollte stattdessen der\ + \ Datentyp BP_Dachgestaltung (Attribut dachgestaltung) verwendet werden." + DNmin: + doc: "Minimal zulässige Dachneigung bei einer Bereichsangabe.\r\n\r\nDies Attribut\ + \ ist veraltet und wird in Version 6.0 wegfallen. Es sollte stattdessen der\ + \ Datentyp BP_Dachgestaltung (Attribut dachgestaltung) verwendet werden." + FR: + doc: Vorgeschriebene Firstrichtung + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximalzahl von oberirdischen zurückgesetzten Vollgeschossen als Staffelgeschoss.. + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + abweichendeBauweise: + doc: Nähere Bezeichnung einer "Abweichenden Bauweise" ("bauweise" == 3000). + bauweise: + doc: Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB). + bebauungsArt: + doc: Detaillierte Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB). + dachform: + doc: "Erlaubte Dachformen.\r\n\r\nDies Attribut ist veraltet und wird in Version\ + \ 6.0 wegfallen. Es sollte stattdessen der Datentyp BP_Dachgestaltung (Attribut\ + \ dachgestaltung) verwendet werden." + dachgestaltung: + doc: Parameter zur Einschränkung der zulässigen Dachformen. + detaillierteDachform: + doc: "Über eine Codeliste definiertere detailliertere Dachform.\r\nDer an einer\ + \ bestimmten Listenposition aufgeführte Wert von \"detaillierteDachform\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von dachform.\r\ + \n\r\nDies Attribut ist veraltet und wird in Version 6.0 wegfallen. Es sollte\ + \ stattdessen der Datentyp BP_Dachgestaltung (Attribut dachgestaltung) verwendet\ + \ werden." + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Festlegung der Zweckbestimmung.\r\ + \nDer an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + traeger: + doc: Trägerschaft einer Gemeinbedarfs-Fläche + zugunstenVon: + doc: Angabe des Begünstigten einer Ausweisung. + zweckbestimmung: + doc: Zweckbestimmung der festgesetzten Fläche + doc: Einrichtungen und Anlagen zur Versorgung mit Gütern und Dienstleistungen des + öffentlichen und privaten Bereichs, hier Flächen für den Gemeindebedarf (§9, Abs. + 1, Nr.5 und Abs. 6 BauGB). +BP_GemeinschaftsanlagenFlaeche: + attributes: + Zmax: + doc: Maximale Anzahl von Garagen-Geschossen + detaillierteZweckbestimmung: + doc: Über eine Codelist definierte detailliertere Festlegung der Zweckbestimmung. + Der an einer bestimmten Listenposition aufgeführte Wert von "detaillierteZweckbestimmung" + bezieht sich auf den an gleicher Position stehenden Attributwert von "zweckbestimmung". + eigentuemer: + doc: Relation auf die Baugebietsfläche, zu der die Gemeinschaftsanlagen-Fläche + gehört. + zweckbestimmung: + doc: Zweckbestimmung der Fläche + doc: Fläche für Gemeinschaftsanlagen für bestimmte räumliche Bereiche wie Kinderspielplätze, + Freizeiteinrichtungen, Stellplätze und Garagen (§ 9 Abs. 1 Nr. 22 BauGB) +BP_GemeinschaftsanlagenZuordnung: + attributes: + zuordnung: + doc: Relation auf die zugeordneten Gemeinschaftsanlagen-Flächen, die außerhalb + des Baugebiets liegen. + doc: "Zuordnung von Gemeinschaftsanlagen zu Grundstücken.\r\n" +BP_GenerischesObjekt: + attributes: + zweckbestimmung: + doc: Über eine CodeList definierte Zweckbestimmung des Objektes. + doc: Klasse zur Modellierung aller Inhalte des Bebauungsplans,die durch keine andere + spezifische XPlanung Klasse repräsentiert werden können. +BP_Geometrieobjekt: + attributes: + flaechenschluss: + doc: "Zeigt bei flächenhaftem Raumbezug an, ob das Objekt als Flächenschlussobjekt\ + \ oder Überlagerungsobjekt gebildet werden soll.\r\nFlächenschlussobjekte\ + \ dürfen sich nicht überlappen, sondern nur an den Flächenrändern berühren,\ + \ wobei die jeweiligen Stützpunkte der Randkurven übereinander liegen müssen.\ + \ Die Vereinigung der Flächenschlussobjekte überdeckt den Geltungsbereich\ + \ des Bebauungsplans vollständig. " + flussrichtung: + doc: "Das Attribut ist nur relevant, wenn ein Geometrieobjekt einen linienhaften\ + \ Raumbezug hat. Ist es mit dem Wert true belegt, wird damit ausgedrückt,\ + \ dass der Linie eine Flussrichtung in Digitalisierungsrichtung, bei Attributwert\ + \ \"false\" gegen die Digitalisierungsrichtung zugeordnet ist. In diesem Fall\ + \ darf bei Im- und Export die Digitalisierungsreihenfolge der Stützpunkte\ + \ nicht geändert werden.Wie eine definierte Flussrichtung zu interpretieren\ + \ oder bei einer Plandarstellung zu visualisieren ist, bleibt der Implementierung\ + \ überlassen.\r\nIst der Attributwert false oder das Attribut nicht belegt,\ + \ ist die Digitalisierungsreihenfolge der Stützpunkte irrelevant." + nordwinkel: + doc: Orientierung des Objektes bei punktförmigem Raumbezug als Winkel gegen + die Nordrichtung. Zählweise im geographischen Sinn (von Nord über Ost nach + Süd und West). + position: + doc: Raumbezug - Entweder punktförmig, linienförmig oder flächenhaft, gemischte + Geometrie ist nicht zugelassen. + doc: Basisklasse für alle Objekte eines Bebauungsplans mit variablem Raumbezug. + Das bedeutet, die abgeleiteten Objekte können kontextabhängig mit Punkt-, Linien- + oder Flächengeometrie gebildet. Die Aggregation von Punkten, Linien oder Flächen + ist zulässig, nicht aber die Mischung von Punkt-, Linien- und Flächengeometrie. +BP_GewaesserFlaeche: + attributes: + detaillierteZweckbestimmung: + doc: Über eine Codeliste definierte detailliertere Zweckbestimmung der Fläche. + zweckbestimmung: + doc: Zweckbestimmung der Wasserfläche. + doc: "Festsetzung neuer Wasserflächen nach §9 Abs. 1 Nr. 16a BauGB.\r\n" +BP_GruenFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximalzahl von oberirdischen zurückgesetzten Vollgeschossen als Staffelgeschoss.. + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Festlegung der Zweckbestimmung.\r\ + \nDer an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + nutzungsform: + doc: Nutzungsform der festgesetzten Fläche. + zugunstenVon: + doc: Angabe des Begünstigten einer Ausweisung. + zweckbestimmung: + doc: Zweckbestimmung der Grünfläche + doc: Festsetzungen von öffentlichen und privaten Grünflächen (§ 9, Abs. 1, Nr. 15 + BauGB). +BP_HoehenMass: + attributes: {} + doc: 'Festsetzungen nach §9 Abs. 1 Nr. 1 BauGB für übereinanderliegende Geschosse + und Ebenen und sonstige Teile baulicher Anlagen (§9 Abs.3 BauGB), sowie Hinweise + auf Geländehöhen. Die Höhenwerte werden über das Attribut ' +BP_Immissionsschutz: + attributes: + detaillierteTechnVorkehrung: + doc: Detaillierte Klassifizierung der auf der Fläche zu treffenden baulichen + oder sonstigen technischen Vorkehrungen + laermpegelbereich: + doc: Festlegung der erforderlichen Luftschalldämmung von Außenbauteilen nach + DIN 4109. + nutzung: + doc: Festgesetzte Nutzung einer Schutzfläche + technVorkehrung: + doc: Klassifizierung der auf der Fläche zu treffenden baulichen oder sonstigen + technischen Vorkehrungen + typ: + doc: Differenzierung der Immissionsschutz-Fläche + doc: Festsetzung einer von der Bebauung freizuhaltenden Schutzfläche und ihre Nutzung, + sowie einer Fläche für besondere Anlagen und Vorkehrungen zum Schutz vor schädlichen + Umwelteinwirkungen und sonstigen Gefahren im Sinne des Bundes-Immissionsschutzgesetzes + sowie die zum Schutz vor solchen Einwirkungen oder zur Vermeidung oder Minderung + solcher Einwirkungen zu treffenden baulichen und sonstigen technischen Vorkehrungen + (§9, Abs. 1, Nr. 24 BauGB). +BP_KennzeichnungsFlaeche: + attributes: + istVerdachtsflaeche: + doc: Legt fest, ob eine Altlast-Verdachtsfläche vorliegt + nummer: + doc: Nummer im Altlastkataster + zweckbestimmung: + doc: Zweckbestimmung der Kennzeichnungs-Fläche. + doc: Flächen für Kennzeichnungen gemäß §9 Abs. 5 BauGB. +BP_KleintierhaltungFlaeche: + attributes: {} + doc: Fläche für die Errichtung von Anlagen für die Kleintierhaltung wie Ausstellungs- + und Zuchtanlagen, Zwinger, Koppeln und dergleichen (§ 9 Abs. 1 Nr. 19 BauGB). +BP_Landwirtschaft: + attributes: + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Zweckbestimmung.\r\nDer\ + \ an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + zweckbestimmung: + doc: "Zweckbestimmungen der Ausweisung.\r\n" + doc: "Festsetzungen für die Landwirtschaft (§ 9, Abs. 1, Nr. 18a BauGB)\r\n" +BP_LandwirtschaftsFlaeche: + attributes: + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Zweckbestimmung.\r\nDer\ + \ an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + zweckbestimmung: + doc: "Zweckbestimmungen der Ausweisung.\r\n" + doc: Festsetzungen für die Landwirtschaft (§ 9, Abs. 1, Nr. 18a BauGB) +BP_Linienobjekt: + attributes: + position: + doc: Linienförmiger Raumbezug (Einzelne zusammenhängende Kurve, die aus Linienstücken + und Kreisbögen aufgebaut sit, oder eine Menge derartiger Kurven), + doc: Basisklasse für alle Objekte eines Bebauungsplans mit linienförmigem Raumbezug + (Eine einzelne zusammenhängende Kurve, die aus Linienstücken und Kreisbögen zusammengesetzt + sein kann, oder eine Menge derartiger Kurven). +BP_NebenanlagenAusschlussFlaeche: + attributes: + abweichungText: + doc: Textliche Beschreibung der Einschränkung oder des Ausschlusses. + typ: + doc: Art des Ausschlusses. + doc: Festsetzung einer Fläche für die Einschränkung oder den Ausschluss von Nebenanlagen + nach §14 Absatz 1 Satz 3 BauNVO. +BP_NebenanlagenFlaeche: + attributes: + Zmax: + doc: Maximale Anzahl der Garagengeschosse. + detaillierteZweckbestimmung: + doc: "Über eine CodeList definierte detailliertere Festlegung der Zweckbestimmung.\ + \ \r\nDer an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + zweckbestimmung: + doc: Zweckbestimmung der Nebenanlagen-Fläche + doc: Fläche für Nebenanlagen, die auf Grund anderer Vorschriften für die Nutzung + von Grundstücken erforderlich sind, wie Spiel-, Freizeit- und Erholungsflächen + sowie die Fläche für Stellplätze und Garagen mit ihren Einfahrten (§9 Abs. 1 Nr. + 4 BauGB) +BP_NichtUeberbaubareGrundstuecksflaeche: + attributes: + nutzung: + doc: Zulässige Nutzung der Fläche + doc: Festlegung der nicht-überbaubaren Grundstücksfläche +BP_NutzungsartenGrenze: + attributes: + detailTyp: + doc: Detaillierter Typ der Abgrenzung, wenn das Attribut typ den Wert 9999 (Sonstige + Abgrenzung) hat. + typ: + doc: Typ der Abgrenzung. Wenn das Attribut nicht belegt ist, ist die Abgrenzung + eine Nutzungsarten-Grenze (Schlüsselnummer 1000). + doc: Abgrenzung unterschiedlicher Nutzung, z.B. von Baugebieten wenn diese nach + PlanzVO in der gleichen Farbe dargestellt werden, oder Abgrenzung unterschiedlicher + Nutzungsmaße innerhalb eines Baugebiets ("Knödellinie", § 1 Abs. 4, § 16 Abs. + 5 BauNVO). +BP_Objekt: + attributes: + laermkontingent: + doc: Festsetzung eines Lärmemissionskontingent nach DIN 45691 + laermkontingentGebiet: + doc: Festsetzung von Lärmemissionskontingenten nach DIN 45691, die einzelnen + Immissionsgebieten zugeordnet sind + rechtscharakter: + doc: Rechtliche Charakterisierung des Planinhaltes. + refTextInhalt: + doc: Referenz eines raumbezogenen Fachobjektes auf textuell formulierte Planinhalte, + insbesondere textliche Festsetzungen. + richtungssektorGrenze: + doc: Zuordnung einer Richtungssektor-Grenze für die Festlegung zusätzlicher + Lärmkontingente + wirdAusgeglichenDurchABE: + doc: Referenz auf eine Anpflanzungs-, Bindungs- oder Erhaltungsmaßnahme, durch + die ein Eingriff ausgeglichen wird. + wirdAusgeglichenDurchFlaeche: + doc: Referenz auf Ausgleichsfläche, die den Eingriff ausgleicht. + wirdAusgeglichenDurchMassnahme: + doc: Verweis auf eine Ausgleichsmaßnahme, die einen vorgenommenen Eingriff + ausgleicht. + wirdAusgeglichenDurchSPEFlaeche: + doc: Referenz auf eine Schutz-, Pflege- oder Entwicklungs-Fläche, die den Eingriff + ausgleicht. + wirdAusgeglichenDurchSPEMassnahme: + doc: Referenz auf eine Schutz-, Pflege- oder Entwicklungsmaßnahme, durch die + ein Eingriff ausgeglichen wird. + zusatzkontingent: + doc: Festsetzung von Zusatzkontingenten für die Lärmemission, die einzelnen + Richtungssektoren zugeordnet sind. Die einzelnen Richtungssektoren werden + parametrisch definiert. + zusatzkontingentFlaeche: + doc: Festsetzung von Zusatzkontingenten für die Lärmemission, die einzelnen + Richtungssektoren zugeordnet sind. Die einzelnen Richtungssektoren werden + durch explizite Flächen definiert. + doc: Basisklasse für alle raumbezogenen Festsetzungen, Hinweise, Vermerke und Kennzeichnungen + eines Bebauungsplans. +BP_PersGruppenBestimmteFlaeche: + attributes: {} + doc: "Fläche, auf denen ganz oder teilweise nur Wohngebäude errichtet werden dürfen,\ + \ die für Personengruppen mit besonderem Wohnbedarf bestimmt sind (§9, Abs. 1,\ + \ Nr. 8 BauGB)\r\n" +BP_Plan: + attributes: + aenderungenBisDatum: + doc: Datum der berücksichtigten Plan-Änderungen. + aufstellungsbeschlussDatum: + doc: Datum des Aufstellungsbeschlusses. + ausfertigungsDatum: + doc: Datum der Ausfertigung. + auslegungsEndDatum: + doc: End-Datum des Auslegungs-Zeitraums. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + auslegungsStartDatum: + doc: Start-Datum des Auslegungs-Zeitraums. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + bereich: + doc: Referenz eines Bebauungsplans auf einen Bereich + durchfuehrungsVertrag: + doc: Gibt an, ob für das Planungsgebiet einen Durchführungsvertrag (Kombination + aus Städtebaulichen Vertrag und Erschließungsvertrag) gibt. + erschliessungsVertrag: + doc: Gibt an, ob es für den Plan einen Erschließungsvertrag gibt. + gemeinde: + doc: Die für den Plan zuständige Gemeinde. + gruenordnungsplan: + doc: Gibt an, ob für den Plan ein zugehöriger Grünordnungsplan existiert. + hoehenbezug: + doc: Bei Höhenangaben im Plan standardmäßig verwendeter Höhenbezug (z.B. Höhe + über NN). + inkrafttretensDatum: + doc: Datum des Inkrafttretens. + planArt: + doc: Typ des vorliegenden Bebauungsplans. + planaufstellendeGemeinde: + doc: Die für die ursprüngliche Planaufstellung zuständige Gemeinde, falls diese + nicht unter dem Attribut gemeinde aufgeführt ist. Dies kann z.B. nach Gemeindefusionen + der Fall sein. + plangeber: + doc: Für den Plan verantwortliche Stelle. + rechtsstand: + doc: Aktueller Rechtsstand des Plans. + rechtsverordnungsDatum: + doc: Datum der Rechtsverordnung. + satzungsbeschlussDatum: + doc: Datum des Satzungsbeschlusses. + sonstPlanArt: + doc: Über eine Codeliste spezifizierte "Sonstige Planart", wenn das Attribut + "planArt" den Wert 9999 (Sonstiges) hat. + staedtebaulicherVertrag: + doc: Gibt an, ob es zum Plan einen städtebaulichen Vertrag gibt. + status: + doc: Über eine Codeliste definierter aktueller Status des Plans. + traegerbeteiligungsEndDatum: + doc: End-Datum der Trägerbeteiligung. Bei mehrfacher Trägerbeteiligung können + mehrere Datumsangaben spezifiziert werden. + traegerbeteiligungsStartDatum: + doc: Start-Datum der Trägerbeteiligung. Bei mehrfacher Trägerbeteiligung können + mehrere Datumsangaben spezifiziert werden. + veraenderungssperre: + doc: "Gibt an, ob es im gesamten Geltungsbereich des Plans eine Veränderungssperre\ + \ gibt.\r\nDies Attribut ist als \"veraltet\" gekennzeichnet und wird in der\ + \ nächsten Hauptversion des Standards wegfallen. Es sollte der Gültigkeitszeitraum\ + \ der Veränderungssperre spezifiziert werden." + veraenderungssperreBeschlussDatum: + doc: Beschlussdatum der Veränderungssperre im gesamten Geltungsbereich + veraenderungssperreDatum: + doc: Datum, ab dem die Veränderungssperre im gesamten Geltungsbereich gilt + veraenderungssperreEndDatum: + doc: Enddatum der Veränderungssperre im gesamten Geltungsbereich + verfahren: + doc: Verfahrensart der BPlan-Aufstellung oder -Änderung. + verlaengerungVeraenderungssperre: + doc: Gibt an, ob die Veränderungssperre bereits ein- oder zweimal verlängert + wurde + versionBauGBDatum: + doc: Bekanntmachungs-Datum der zugrunde liegenden Version des BauGB. + versionBauGBText: + doc: Textliche Spezifikation der zugrunde liegenden Version des BauGB. + versionBauNVODatum: + doc: Bekanntmachungs-Datum der zugrunde liegenden Version der BauNVO + versionBauNVOText: + doc: Textliche Spezifikation der zugrunde liegenden Version der BauNVO + versionSonstRechtsgrundlageDatum: + doc: Bekanntmachungs-Datum einer zugrunde liegenden anderen Rechtsgrundlage + als BauGB / BauNVO. + versionSonstRechtsgrundlageText: + doc: Textliche Spezifikation einer zugrunde liegenden anderen Rechtsgrundlage + als BauGB / BauNVO. + doc: Die Klasse modelliert einen Bebauungsplan +BP_Punktobjekt: + attributes: + nordwinkel: + doc: Orientierung des Punktobjektes als Winkel gegen die Nordrichtung. Zählweise + im geographischen Sinn (von Nord über Ost nach Süd und West). + position: + doc: Punktförmiger Raumbezug (Einzelpunkt oder Punktmenge). + doc: Basisklasse für alle Objekte eines Bebauungsplans mit punktförmigem Raumbezug + (Einzelpunkt oder Punktmenge). +BP_RegelungVergnuegungsstaetten: + attributes: + zulaessigkeit: + doc: "Zulässigkeit von Vergnügungsstätten.\r\n" + doc: Festsetzung nach §9 Abs. 2b BauGB (Zulässigkeit von Vergnügungsstätten). +BP_RekultivierungsFlaeche: + attributes: {} + doc: "Rekultivierungs-Fläche\r\n" +BP_Richtungssektor: + attributes: + winkelAnfang: + doc: Startwinkel des Emissionssektors + winkelEnde: + doc: Endwinkel des Emissionssektors + zkWertNacht: + doc: Zusatzkontingent Nacht + zkWertTag: + doc: Zusatzkontingent Tag + doc: Spezifikation von Zusatzkontingenten Tag/Nacht der Lärmemission für einen Richtungssektor +BP_RichtungssektorGrenze: + attributes: + winkel: + doc: Richtungswinkel der Sektorengrenze + doc: Linienhafte Repräsentation einer Richtungssektor-Grenze +BP_SchutzPflegeEntwicklungsFlaeche: + attributes: + istAusgleich: + doc: Gibt an, ob die Fläche zum Ausgleich von Eingriffen genutzt wird. + massnahme: + doc: Durchzuführende Maßnahme. + refLandschaftsplan: + doc: Referenz auf den Landschaftsplan + refMassnahmenText: + doc: Referenz auf ein Dokument zur Beschreibung der durchzuführenden Maßnahmen. + sonstZiel: + doc: Textlich formuliertes Ziel, wenn das Attribut ziel den Wert 9999 (Sonstiges) + hat. + ziel: + doc: Ziel der auf der Fläche durchzuführenden Maßnahmen. + doc: Umgrenzung von Flächen für Maßnahmen zum Schutz, zur Pflege und zur Entwicklung + von Natur und Landschaft (§9 Abs. 1 Nr. 20 und Abs. 4 BauGB) +BP_SchutzPflegeEntwicklungsMassnahme: + attributes: + istAusgleich: + doc: Gibt an, ob die Maßnahme zum Ausgleich von Eingriffen genutzt wird. + massnahme: + doc: Durchzuführende Maßnahme. + refLandschaftsplan: + doc: Referenz auf den Landschaftsplan. + refMassnahmenText: + doc: Referenz auf ein Dokument, das die durchzuführenden Maßnahmen beschreibt. + sonstZiel: + doc: Textlich formuliertes Ziel, wenn das Aztribut ziel den Wert 9999 (Sonstiges) + hat. + ziel: + doc: Ziel der Maßnahme + doc: Maßnahmen zum Schutz, zur Pflege und zur Entwicklung von Natur und Landschaft + (§9 Abs. 1 Nr. 20 und Abs. 4 BauGB). +BP_Sichtflaeche: + attributes: + art: + doc: Klassifikation der Einmündung einer untergeordneten auf eine übergeordnete + Straße gemäß den "Richtlinien für die Anlage von Stadtstraßen" (TAST 06) + geschwindigkeit: + doc: Zulässige Geschwindigkeit in der übergeordneten Straße, im km/h + knotenpunkt: + doc: Klassifikation des Knotenpunktes, dem die Sichtfläche zugeordnet ist + schenkellaenge: + doc: Schenkellänge des Sichtdreiecks gemäß RAST 06 + doc: "Flächenhafte Festlegung einer Sichtfläche bzw. eines Sichtdreiecks\r\n" +BP_SpezielleBauweise: + attributes: + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken. + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + sonstTyp: + doc: Über eine Codeliste definierter Typ der speziellen Bauweise, wenn typ den + Wert 9999 (Sonstiges) hat. + typ: + doc: Typ der speziellen Bauweise. + wegerecht: + doc: Relation auf Angaben zu Wegerechten. + doc: Festsetzung der speziellen Bauweise / baulichen Besonderheit eines Gebäudes + oder Bauwerks. +BP_SpielSportanlagenFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximalzahl von oberirdischen zurückgesetzten Vollgeschossen als Staffelgeschoss.. + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Festlegung der Zweckbestimmung.\r\ + \nDer an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + zugunstenVon: + doc: Angabe des Begünstigten einer Ausweisung. + zweckbestimmung: + doc: Zweckbestimmung der festgesetzten Fläche. + doc: Einrichtungen und Anlagen zur Versorgung mit Gütern und Dienstleistungen des + öffentlichen und privaten Bereichs, hier Flächen für Sport- und Spielanlagen (§9, + Abs. 1, Nr. 5 und Abs. 6 BauGB). +BP_StrassenVerkehrsFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximalzahl von oberirdischen zurückgesetzten Vollgeschossen als Staffelgeschoss.. + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + begrenzungslinie: + doc: Referenz auf eine Linie, die die Verkehrsfläche begrenzt. + nutzungsform: + doc: Nutzungsform der Fläche + doc: Strassenverkehrsfläche (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB) . +BP_StrassenbegrenzungsLinie: + attributes: + bautiefe: + doc: Minimaler Abstand der Bebauung von der Straßenbegrenzungslinie. + doc: "Straßenbegrenzungslinie (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB) .\r\n" +BP_Strassenkoerper: + attributes: + typ: + doc: Notwendige Maßnahme zur Herstellung des Straßenkörpers. + doc: "Flächen für Aufschüttungen, Abgrabungen und Stützmauern, soweit sie zur Herstellung\ + \ des Straßenkörpers erforderlich sind (§9, Abs. 1, Nr. 26 BauGB).\r\n" +BP_TechnischeMassnahmenFlaeche: + attributes: + technischeMassnahme: + doc: Beschreibung der Maßnahme + zweckbestimmung: + doc: Klassifikation der durchzuführenden Maßnahmen nach §9, Abs. 1, Nr. 23 BauGB. + doc: Fläche für technische oder bauliche Maßnahmen nach § 9, Abs. 1, Nr. 23 BauGB. +BP_TextAbschnitt: + attributes: + rechtscharakter: + doc: "Rechtscharakter des textlich formulierten Planinhalts.\r\n" + doc: 'Texlich formulierter Inhalt eines Bebauungsplans, der einen anderen Rechtscharakter + als das zugrunde liegende Fachobjekt hat (Attribut ' +BP_TextlicheFestsetzungsFlaeche: + attributes: {} + doc: Bereich, in dem bestimmte Textliche Festsetzungen gültig sind, die über die + Relation " +BP_UeberbaubareGrundstuecksFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + DN: + doc: "Maximal zulässige Dachneigung.\r\n\r\nDies Attribut ist veraltet und wird\ + \ in Version 6.0 wegfallen. Es sollte stattdessen der Datentyp BP_Dachgestaltung\ + \ (Attribut dachgestaltung) verwendet werden." + DNZwingend: + doc: "Zwingend vorgeschriebene Dachneigung.\r\n\r\nDies Attribut ist veraltet\ + \ und wird in Version 6.0 wegfallen. Es sollte stattdessen der Datentyp BP_Dachgestaltung\ + \ (Attribut dachgestaltung) verwendet werden." + DNmax: + doc: "Maxmal zulässige Dachneigung bei einer Bereichsangabe.\r\n\r\nDies Attribut\ + \ ist veraltet und wird in Version 6.0 wegfallen. Es sollte stattdessen der\ + \ Datentyp BP_Dachgestaltung (Attribut dachgestaltung) verwendet werden." + DNmin: + doc: "Minimal zulässige Dachneigung bei einer Bereichsangabe.\r\n\r\nDies Attribut\ + \ ist veraltet und wird in Version 6.0 wegfallen. Es sollte stattdessen der\ + \ Datentyp BP_Dachgestaltung (Attribut dachgestaltung) verwendet werden." + FR: + doc: Vorgeschriebene Firstrichtung + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFAntGewerbe: + doc: "Festsetzung nach §6a Abs. (4) Nr. 4 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass \r\nin Gebäuden ein im Bebauungsplan\ + \ bestimmter Anteil der zulässigen \r\nGeschossfläche für gewerbliche Nutzungen\ + \ zu verwenden ist." + GFAntWohnen: + doc: "Festsetzung nach §4a Abs. (4) Nr. 2 bzw. §6a Abs. (4) Nr. 3 BauNVO: Für\ + \ besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann\ + \ festgesetzt werden, dass \r\nin Gebäuden ein im Bebauungsplan bestimmter\ + \ Anteil der zulässigen \r\nGeschossfläche für Wohnungen zu verwenden ist." + GFGewerbe: + doc: "Festsetzung nach §6a Abs. (4) Nr. 4 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass \r\nin Gebäuden eine im Bebauungsplan\ + \ bestimmte Größe der Geschossfläche für gewerbliche Nutzungen zu verwenden\ + \ ist." + GFWohnen: + doc: "Festsetzung nach §4a Abs. (4) Nr. 2 bzw. §6a Abs. (4) Nr. 3 BauNVO: Für\ + \ besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann\ + \ festgesetzt werden, dass \r\nin Gebäuden eine im Bebauungsplan bestimmte\ + \ Größe der Geschossfläche für Wohnungen zu verwenden ist." + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + VF: + doc: Festsetzung der maximal zulässigen Verkaufsfläche in einem Sondergebiet + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + ZWohn: + doc: 'Festsetzung nach §4a Abs. (4) Nr. 1 bzw. nach §6a Abs. (4) Nr. 2 BauNVO: + Für besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann + festgesetzt werden, dass in Gebäuden oberhalb eines im Bebauungsplan bestimmten + Geschosses nur Wohnungen zulässig sind.' + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximalzahl von oberirdischen zurückgesetzten Vollgeschossen als Staffelgeschoss.. + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + abweichendeBauweise: + doc: "Nähere Bezeichnung einer \"Abweichenden Bauweise\" (\"bauweise == 3000\"\ + ).\r\nDieser Wert hat Priorität gegenüber einer im umschließenden Baugebiet\ + \ (BP_BaugebietsTeilFlaeche) getroffenen Festsetzung" + baugrenze: + doc: Referenz auf eine Baugrenze, die auf der Randkurve der Fläche verläuft. + baulinie: + doc: Referenz auf eine Bauliniedie auf der Randkurve der Fläche verläuft. + bauweise: + doc: "Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB).\r\nDieser Wert hat\ + \ Priorität gegenüber einer im umschließenden Baugebiet (BP_BaugebietsTeilFlaeche)\ + \ getroffenen Festsetzung" + bebauungRueckwaertigeGrenze: + doc: "Festsetzung der Bebauung der rückwärtigen Grundstücksgrenze (§9, Abs.\ + \ 1, Nr. 2 BauGB).\r\nDieser Wert hat Priorität gegenüber einer im umschließenden\ + \ Baugebiet (BP_BaugebietsTeilFlaeche) getroffenen Festsetzung." + bebauungSeitlicheGrenze: + doc: "Festsetzung der Bebauung der seitlichen Grundstücksgrenze (§9, Abs. 1,\ + \ Nr. 2 BauGB).\r\nDieser Wert hat Priorität gegenüber einer im umschließenden\ + \ Baugebiet (BP_BaugebietsTeilFlaeche) getroffenen Festsetzung." + bebauungVordereGrenze: + doc: "Festsetzung der Bebauung der vorderen Grundstücksgrenze (§9, Abs. 1, Nr.\ + \ 2 BauGB).\r\nDieser Wert hat Priorität gegenüber einer im umschließenden\ + \ Baugebiet (BP_BaugebietsTeilFlaeche) getroffenen Festsetzung." + bebauungsArt: + doc: "Detaillierte Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB).\r\nDieser\ + \ Wert hat Priorität gegenüber einer im umschließenden Baugebiet (BP_BaugebietsTeilFlaeche)\ + \ getroffenen Festsetzung." + dachform: + doc: "Erlaubte Dachformen.\r\n\r\nDies Attribut ist veraltet und wird in Version\ + \ 6.0 wegfallen. Es sollte stattdessen der Datentyp BP_Dachgestaltung (Attribut\ + \ dachgestaltung) verwendet werden." + dachgestaltung: + doc: Parameter zur Einschränkung der zulässigen Dachformen. + detaillierteDachform: + doc: "Über eine Codeliste definiertere detailliertere Dachform.\r\nDer an einer\ + \ bestimmten Listenposition aufgeführte Wert von \"detaillierteDachform\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von dachform.\r\ + \n\r\nDies Attribut ist veraltet und wird in Version 6.0 wegfallen. Es sollte\ + \ stattdessen der Datentyp BP_Dachgestaltung (Attribut dachgestaltung) verwendet\ + \ werden." + geschossMax: + doc: Gibt bei geschossweiser Festsetzung die Nummer des Geschosses an, bis zu + der die Festsetzung gilt. Wenn das Attribut nicht belegt ist, gilt die Festsetzung + für alle Geschosse ab einschl. "geschossMin". + geschossMin: + doc: Gibt bei geschossweiser Festsetzung die Nummer des Geschosses an, ab den + die Festsetzung gilt. Wenn das Attribut nicht belegt ist, gilt die Festsetzung + für alle Geschosse bis einschl. "geschossMax". + refGebaeudequerschnitt: + doc: "Referenz auf ein Dokument mit vorgeschriebenen Gebäudequerschnitten.\r\ + \nDieser Wert hat Priorität gegenüber einer im umschließenden Baugebiet (BP_BaugebietsTeilFlaeche)\ + \ getroffenen Festsetzung." + vertikaleDifferenzierung: + doc: "Gibt an, ob eine vertikale Differenzierung der Gebäude vorgeschrieben\ + \ ist.\r\nDieser Wert hat Priorität gegenüber einer im umschließenden Baugebiet\ + \ (BP_BaugebietsTeilFlaeche) getroffenen Festsetzung." + wohnnutzungEGStrasse: + doc: "Festsetzung nach §6a Abs. (4) Nr. 1 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass in Gebäuden \r\nim Erdgeschoss\ + \ an der Straßenseite eine Wohnnutzung nicht oder nur ausnahmsweise zulässig\ + \ ist." + doc: 'Festsetzung der überbaubaren Grundstücksfläche (§9, Abs. 1, Nr. 2 BauGB). + Über die Attribute ' +BP_Ueberlagerungsobjekt: + attributes: {} + doc: Basisklasse für alle Objekte eines Bebauungsplans mit flächenhaftem Raumbezug, + die immer Überlagerungsobjekte sind. +BP_UnverbindlicheVormerkung: + attributes: + vormerkung: + doc: Text der Vormerkung. + doc: "Unverbindliche Vormerkung späterer Planungsabsichten.\r\n" +BP_VerEntsorgung: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximalzahl von oberirdischen zurückgesetzten Vollgeschossen als Staffelgeschoss.. + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Zweckbestimmung der Festsetzung\r\ + \nDer an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + textlicheErgaenzung: + doc: Zusätzliche textliche Beschreibung der Ver- bzw. Entsorgungseinrichtung. + zugunstenVon: + doc: Angabe des Begünstigten der Ausweisung. + zweckbestimmung: + doc: Zweckbestimmung der Festsetzung.. + doc: Flächen und Leitungen für Versorgungsanlagen, für die Abfallentsorgung und + Abwasserbeseitigung sowie für Ablagerungen (§9 Abs. 1, Nr. 12, 14 und Abs. 6 BauGB) +BP_Veraenderungssperre: + attributes: + gueltigkeitsDatum: + doc: Datum, bis zu dem die Veränderungssperre bestehen bleibt. + refBeschluss: + doc: Referenz auf das Dokument mit dem zug. Beschluss. + veraenderungssperreBeschlussDatum: + doc: Beschlussdatum der Veränderungssperre im Teilbereich + veraenderungssperreStartDatum: + doc: "Startdatum der Veränderungssperre im Teilbereich.\r\nIn der nächsten Hauptversion\ + \ wird dies Attribut verpflichtend zu belegen sein." + verlaengerung: + doc: Gibt an, ob die Veränderungssperre bereits ein- oder zweimal verlängert + wurde. + doc: Ausweisung einer Veränderungssperre, die nicht den gesamten Geltungsbereich + des Plans umfasst. Bei Verwendung dieser Klasse muss das Attribut " +BP_VerkehrsflaecheBesondererZweckbestimmung: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximalzahl von oberirdischen zurückgesetzten Vollgeschossen als Staffelgeschoss.. + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + begrenzungslinie: + doc: Referenz auf eine Linie, die die Verkehrsfläche begrenzt. + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Zweckbestimmung der Fläche.\r\ + \nDer an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + nutzungsform: + doc: Nutzungsform der Fläche. + zugunstenVon: + doc: Begünstigter der Festsetzung + zweckbestimmung: + doc: Zweckbestimmung der Fläche + doc: Verkehrsfläche besonderer Zweckbestimmung (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB). +BP_WaldFlaeche: + attributes: + betreten: + doc: Festlegung zusätzlicher, normalerweise nicht-gestatteter Aktivitäten, die + in dem Wald ausgeführt werden dürfen, nach §14 Abs. 2 Bundeswaldgesetz. + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Festlegung der Funktion\ + \ des Waldes.\r\nDer an einer bestimmten Listenposition aufgeführte Wert von\ + \ \"detaillierteZweckbestimmung\" bezieht sich auf den an gleicher Position\ + \ stehenden Attributwert von \"zweckbestimmung\"." + eigentumsart: + doc: Festlegung der Eigentumsart des Waldes + zweckbestimmung: + doc: Funktion der Waldfläche + doc: Festsetzung von Waldflächen (§ 9, Abs. 1, Nr. 18b BauGB). +BP_WasserwirtschaftsFlaeche: + attributes: + detaillierteZweckbestimmung: + doc: Über eine Codeliste definierte detailliertere Zweckbestimmung der Fläche. + zweckbestimmung: + doc: Zweckbestimmung der Fläche. + doc: 'Flächen für die Wasserwirtschaft (§9 Abs. 1 Nr. 16a BauGB), sowie Flächen + für Hochwasserschutz-anlagen und für die Regelung des Wasserabflusses (§9 Abs. + 1 Nr. 16b BauGB). ' +BP_Wegerecht: + attributes: + breite: + doc: Breite des Wegerechts bei linienförmiger Ausweisung der Geometrie. + istSchmal: + doc: Gibt an, ob es sich um eine "schmale Fläche" handelt gem. Planzeichen + 15.5 der PlanZV handelt. + thema: + doc: Beschreibung des Rechtes. + typ: + doc: Typ des Wegerechts + zugunstenVon: + doc: Inhaber der Rechte. + doc: Festsetzung von Flächen, die mit Geh-, Fahr-, und Leitungsrechten zugunsten + der Allgemeinheit, eines Erschließungsträgers, oder eines beschränkten Personenkreises + belastet sind (§ 9 Abs. 1 Nr. 21 und Abs. 6 BauGB). +BP_ZentralerVersorgungsbereich: + attributes: {} + doc: 'Zentraler Versorgungsbereich gem. § 9 Abs. 2a BauGB ' +BP_ZusatzkontingentLaerm: + attributes: + bezeichnung: + doc: Bezeichnung des Kontingentes + richtungssektor: + doc: Spezifikation der Richtungssektoren + doc: Parametrische Spezifikation von zusätzlichen Lärmemissionskontingenten für + einzelne Richtungssektoren (DIN 45691, Anhang 2). +BP_ZusatzkontingentLaermFlaeche: + attributes: + bezeichnung: + doc: Bezeichnung des Kontingentes + richtungssektor: + doc: Spezifikation des zugehörigen Richtungssektors + doc: Flächenhafte Spezifikation von zusätzlichen Lärmemissionskontingenten für einzelne + Richtungssektoren (DIN 45691, Anhang 2). +FP_Abgrabung: + attributes: + abbaugut: + doc: Bezeichnung des Abbauguts. + doc: 'Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen + (§5, Abs. 2, Nr. 8 BauGB). Hier: Flächen für Abgrabungen und die Gewinnung von + Bodenschätzen.' +FP_AnpassungKlimawandel: + attributes: + detailMassnahme: + doc: Detaillierung der durch das Attribut massnahme festgelegten Maßnahme über + eine Codeliste. + massnahme: + doc: Klassifikation der Massnahme + doc: 'Anlagen, Einrichtungen und sonstige Maßnahmen, die der Anpassung an den Klimawandel + dienen ' +FP_Aufschuettung: + attributes: + aufschuettungsmaterial: + doc: Bezeichnung des aufgeschütteten Materials + doc: 'Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen + (§5, Abs. 2, Nr. 8 BauGB). Hier: Flächen für Aufschüttungen.' +FP_AusgleichsFlaeche: + attributes: + massnahme: + doc: Auf der Fläche durchzuführende Maßnahme. + refLandschaftsplan: + doc: Referenz auf den Landschaftsplan. + refMassnahmenText: + doc: Referenz auf ein Dokument in dem die Massnahmen beschrieben werden. + sonstZiel: + doc: Textlich formuliertes Ziel, wenn das Attribut ziel den Wert 9999 (Sonstiges) + hat. + ziel: + doc: Ziel der auf der Fläche durchzuführenden Maßnahmen. + doc: Flächen und Maßnahmen zum Ausgleich gemäß § 5, Abs. 2a BauBG. +FP_BebauungsFlaeche: + attributes: + BMZ: + doc: Angabe einer maximalen Baumassenzahl als Maß der baulichen Nutzung. + GFZ: + doc: Angabe einer maximalen Geschossflächenzahl als Maß der baulichen Nutzung. + GFZmax: + doc: Maximale Geschossflächenzahl bei einer Bereichsangabe (GFZmin muss ebenfalls + spezifiziert werden). + GFZmin: + doc: Minimale Geschossflächenzahl bei einer Bereichsangabe (GFZmax muss ebenfalls + spezifiziert werden). + GRZ: + doc: Angabe einer maximalen Grundflächenzahl als Maß der baulichen Nutzung. + allgArtDerBaulNutzung: + doc: Angabe der allgemeinen Art der baulichen Nutzung. + besondereArtDerBaulNutzung: + doc: Angabe der besonderen Art der baulichen Nutzung. + detaillierteArtDerBaulNutzung: + doc: Über eine Codeliste definierte detailliertere Art der baulichen Nutzung. + detaillierteSondernutzung: + doc: Über eine Codeliste definierte detailliertere Sondernutzung. + nutzungText: + doc: 'Bei Nutzungsform "Sondergebiet": Kurzform der besonderen Art der baulichen + Nutzung.' + sonderNutzung: + doc: Differenziert Sondernutzungen nach §10 und §11 der BauNVO von 1977 und + 1990. Das Attribut wird nur benutzt, wenn besondereArtDerBaulNutzung unbelegt + ist oder einen der Werte 2000 bzw. 2100 hat + doc: Darstellung einer für die Bebauung vorgesehenen Fläche (§ 5, Abs. 2, Nr. 1 + BauGB). +FP_Bereich: + attributes: + gehoertZuPlan: + doc: Referenz auf den Flächennutzungsplan, zu dem das Bereichsobjekt gehört. + versionBauGBDatum: + doc: "Bekanntmachungs-Datum der zugrunde liegenden Version des BauGB.\r\n\r\n\ + Das Attribut ist veraltet und wird in XPlanGML 6.0 wegfallen. Es sollte stattdessen\ + \ das gleichnamige Attribut von BP_Plan verwendet werden." + versionBauGBText: + doc: "Zugrunde liegende Version des BauGB.\r\n\r\nDas Attribut ist veraltet\ + \ und wird in XPlanGML 6.0 wegfallen. Es sollte stattdessen das gleichnamige\ + \ Attribut von BP_Plan verwendet werden." + versionBauNVODatum: + doc: "Bekanntmachungs-Datum der zugrunde liegenden Version der BauNVO\r\n\r\n\ + Das Attribut ist veraltet und wird in XPlanGML 6.0 wegfallen. Es sollte stattdessen\ + \ das gleichnamige Attribut von BP_Plan verwendet werden." + versionBauNVOText: + doc: "Zugrunde liegende Version der BauNVO. \r\n\r\nDas Attribut ist veraltet\ + \ und wird in XPlanGML 6.0 wegfallen. Es sollte stattdessen das gleichnamige\ + \ Attribut von BP_Plan verwendet werden." + versionSonstRechtsgrundlageDatum: + doc: "Bekanntmachungs-Datum einer zugrunde liegenden anderen Rechtsgrundlage\ + \ als BauGB / BauNVO.\r\n\r\nDas Attribut ist veraltet und wird in XPlanGML\ + \ 6.0 wegfallen. Es sollte stattdessen das gleichnamige Attribut von BP_Plan\ + \ verwendet werden." + versionSonstRechtsgrundlageText: + doc: "Textliche Spezifikation einer zugrunde liegenden anderen Rechtsgrundlage\ + \ als BauGB / BauNVO.\r\n\r\nDas Attribut ist veraltet und wird in XPlanGML\ + \ 6.0 wegfallen. Es sollte stattdessen das gleichnamige Attribut von BP_Plan\ + \ verwendet werden." + doc: Diese Klasse modelliert einen Bereich eines Flächennutzungsplans. +FP_Bodenschaetze: + attributes: + abbaugut: + doc: Bezeichnung des Abbauguts. + doc: "Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen\ + \ (§5, Abs. 2, Nr. 8 BauGB. Hier: Flächen für Bodenschätze.\r\n" +FP_DarstellungNachLandesrecht: + attributes: + detailZweckbestimmung: + doc: Über eine Codeliste definierte detaillierte Zweckbestimmung der Planinhalts- + kurzbeschreibung: + doc: Textuelle Beschreibung des Planinhalts. + doc: Inhalt des Flächennutzungsplans, der auf einer spezifischen Rechtsverordnung + eines Bundeslandes beruht. +FP_FlaecheOhneDarstellung: + attributes: {} + doc: Fläche, für die keine geplante Nutzung angegben werden kann +FP_Flaechenobjekt: + attributes: + flaechenschluss: + doc: 'Zeigt an, ob das Objekt als Flächenschlussobjekt oder Überlagerungsobjekt + gebildet werden soll. Flächenschlussobjekte dürfen sich nicht überlappen, + sondern nur an den Flächenrändern berühren, wobei die jeweiligen Stützpunkte + der Randkurven übereinander liegen müssen. Die Vereinigung der Flächenschlussobjekte + überdeckt den Geltungsbereich des Flächennutzungsplans vollständig. ' + position: + doc: 'Flächenhafter Raumbezug des Objektes (Eine Einzelfläche oder eine Menge + von Flächen, die sich nicht überlappen dürfen). ' + doc: 'Basisklasse für alle Objekte eines Flächennutzungsplans mit flächenhaftem + Raumbezug (eine Einzelfläche oder eine Menge von Flächen, die sich nicht überlappen + dürfen). Die von ' +FP_Flaechenschlussobjekt: + attributes: {} + doc: "Basisklasse für alle Objekte eines Flächennutzungsplans mit flächenhaftem\ + \ Raumbezug, die auf Ebene 0 immer Flächenschlussobjekte sind.\r\n" +FP_Gemeinbedarf: + attributes: + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Zweckbestimmung.\r\nDer\ + \ an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + zweckbestimmung: + doc: Zweckbestimmung der Fläche + doc: Darstellung von Flächen für den Gemeinbedarf nach § 5, Abs. 2, Nr. 2 BauGB. +FP_GenerischesObjekt: + attributes: + zweckbestimmung: + doc: Über eine Codeliste definierte Zweckbestimmung des Generischen Objekts. + doc: Klasse zur Modellierung aller Inhalte des FPlans, die durch keine spezifische + XPlanung-Klasse repräsentiert werden können. +FP_Geometrieobjekt: + attributes: + flaechenschluss: + doc: "Zeigt bei flächenhaftem Raumbezug an, ob das Objekt als Flächenschlussobjekt\ + \ oder Überlagerungsobjekt gebildet werden soll.\r\nFlächenschlussobjekte\ + \ dürfen sich nicht überlappen, sondern nur an den Flächenrändern berühren,\ + \ wobei die jeweiligen Stützpunkte der Randkurven übereinander liegen müssen.\ + \ Die Vereinigung der Flächenschlussobjekte überdeckt den Geltungsbereich\ + \ des Flächennutzungsplans vollständig. " + flussrichtung: + doc: "Das Attribut ist nur relevant, wenn ein Geometrieobjekt einen linienhaften\ + \ Raumbezug hat. Ist es mit dem Wert true belegt, wird damit ausgedrückt,\ + \ dass der Linie eine Flussrichtung in Digitalisierungsrichtung zugeordnet\ + \ ist. In diesem Fall darf bei Im- und Export die Digitalisierungsreihenfolge\ + \ der Stützpunkte nicht geändert werden. Wie eine definierte Flussrichtung\ + \ zu interpretieren oder bei einer Plandarstellung zu visualisieren ist,\ + \ bleibt der Implementierung überlassen.\r\nIst der Attributwert false oder\ + \ das Attribut nicht belegt, ist die Digitalisierungsreihenfolge der Stützpunkte\ + \ irrelevant." + nordwinkel: + doc: Orientierung des Objektes bei punkförmigem Raumbezug als Winkel gegen die + Nordrichtung. Zählweise im geographischen Sinn (von Nord über Ost nach Süd + und West). + position: + doc: Raumbezug - Entweder punktförmig, linienförmig oder flächenhaft, gemischte + Geometrie ist nicht zugelassen. + doc: Basisklasse für alle Objekte eines Flächennutzungsplans mit variablem Raumbezug. + Ein konkretes Objekt muss entweder punktförmigen, linienförmigen oder flächenhaften + Raumbezug haben, gemischte Geometrie ist nicht zugelassen. +FP_Gewaesser: + attributes: + detaillierteZweckbestimmung: + doc: "Über eine Codelist definierte detailliertere Zweckbestimmung des Objektes.\r\ + \nDer an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + zweckbestimmung: + doc: Zweckbestimmung des Gewässers. + doc: "Darstellung von Wasserflächen nach §5, Abs. 2, Nr. 7 BauGB.\r\n" +FP_Gruen: + attributes: + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Zweckbestimmung.\r\nDer\ + \ an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + nutzungsform: + doc: Nutzungsform der Grünfläche. + zweckbestimmung: + doc: Zweckbestimmung der Grünfläche. + doc: Darstellung einer Grünfläche nach § 5, Abs. 2, Nr. 5 BauGB, +FP_KeineZentrAbwasserBeseitigungFlaeche: + attributes: {} + doc: Baufläche, für die eine zentrale Abwasserbeseitigung nicht vorgesehen ist (§ + 5, Abs. 2, Nr. 1 BauGB). +FP_Kennzeichnung: + attributes: + istVerdachtsflaeche: + doc: Legt fest, ob eine Altlast-Verdachtsfläche vorliegt + nummer: + doc: Nummer in einem Altlastkataster + zweckbestimmung: + doc: Zweckbestimmung der Kennzeichnung. + doc: Kennzeichnung gemäß §5 Abs. 3 BauGB. +FP_Landwirtschaft: + attributes: + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Zweckbestimmung.\r\nDer\ + \ an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + zweckbestimmung: + doc: Zweckbestimmung der Fläche. + doc: Darstellung einer Landwirtschaftsfläche nach §5, Abs. 2, Nr. 9a. +FP_LandwirtschaftsFlaeche: + attributes: + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Zweckbestimmung.\r\nDer\ + \ an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + zweckbestimmung: + doc: Zweckbestimmung der Fläche. + doc: "Darstellung einer Landwirtschaftsfläche nach §5, Abs. 2, Nr. 9a.\r\n" +FP_Linienobjekt: + attributes: + position: + doc: Linienförmiger Raumbezug (Einzelne zusammenhängende Kurve, die aus Linienstücken + und Kreisbögen aufgebaut ist, oder eine Menge derartiger Kurven). + doc: "Basisklasse für alle Objekte eines Flächennutzungsplans mit linienförmigem\ + \ Raumbezug (eine einzelne zusammenhängende Kurve, die aus Linienstücken und Kreisbögen\ + \ zusammengesetzt sein kann, oder eine Menge derartiger Kurven).\r\n" +FP_NutzungsbeschraenkungsFlaeche: + attributes: {} + doc: "Umgrenzungen der Flächen für besondere Anlagen und Vorkehrungen zum Schutz\ + \ vor schädlichen Umwelteinwirkungen im Sinne des Bundes-\r\n" +FP_Objekt: + attributes: + rechtscharakter: + doc: Rechtliche Charakterisierung des Planinhalts + refTextInhalt: + doc: Referenz eines raumbezogenen Fachobjektes auf textuell formulierte Planinhalte. + spezifischePraegung: + doc: Über eine Codeliste definierte spezifische bauliche Prägung einer Darstellung. + vonGenehmigungAusgenommen: + doc: Angabe, ob Teile des Flächennutzungsplans nach §6 Abs. 3 BauGB von der + Genehmigung ausgenommen sind + wirdAusgeglichenDurchFlaeche: + doc: Referenz auf eine Ausgleichsfläche, die den Eingriff ausgleicht. + wirdAusgeglichenDurchSPE: + doc: Referenz auf eine Schutz-,Pflege- oder Entwicklungsmaßnahme, die den Eingriff + ausgleicht. + doc: Basisklasse für alle Fachobjekte des Flächennutzungsplans. +FP_Plan: + attributes: + aenderungenBisDatum: + doc: Datum, bis zu dem Änderungen des Plans berücksichtigt wurden. + aufstellungsbeschlussDatum: + doc: Datum des Plan-Aufstellungsbeschlusses. + auslegungsEndDatum: + doc: End-Datum der öffentlichen Auslegung. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + auslegungsStartDatum: + doc: Start-Datum der öffentlichen Auslegung. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + bereich: + doc: Referenz auf einen Bereich des Flächennutzungsplans + entwurfsbeschlussDatum: + doc: Datum des Entwurfsbeschlusses + gemeinde: + doc: Zuständige Gemeinde + planArt: + doc: Typ des FPlans + planaufstellendeGemeinde: + doc: Die für die ursprüngliche Planaufstellung zuständige Gemeinde, falls diese + nicht unter dem Attribut gemeinde aufgeführt ist. Dies kann z.B. nach Gemeindefusionen + der Fall sein. + planbeschlussDatum: + doc: Datum des Planbeschlusses + plangeber: + doc: "Für die Planung zuständige Institution\r\n" + rechtsstand: + doc: Aktueller Rechtsstand des Plans. + sachgebiet: + doc: Sachgebiet eines Teilflächennutzungsplans. + sonstPlanArt: + doc: Sonstiger Typ des FPlans bei "planArt" == 9999. + status: + doc: Über eine Codeliste definierter Status des Plans. + traegerbeteiligungsEndDatum: + doc: End-Datum der Trägerbeteiligung. Bei mehrfacher Trägerbeteiligung können + mehrere Datumsangaben spezifiziert werden. + traegerbeteiligungsStartDatum: + doc: Start-Datum der Trägerbeteiligung. Bei mehrfacher Trägerbeteiligung können + mehrere Datumsangaben spezifiziert werden. + verfahren: + doc: Verfahren nach dem ein FPlan aufgestellt oder geändert wird. + versionBauGBDatum: + doc: Bekanntmachungs-Datum der zugrunde liegenden Version des BauGB. + versionBauGBText: + doc: Textliche Spezifikation der zugrunde liegenden Version des BauGB. + versionBauNVODatum: + doc: Bekanntmachungs-Datum der zugrunde liegenden Version der BauNVO + versionBauNVOText: + doc: Textliche Spezifikation der zugrunde liegenden Version der BauNVO + versionSonstRechtsgrundlageDatum: + doc: Bekanntmachungs-Datum einer zugrunde liegenden anderen Rechtsgrundlage + als BauGB / BauNVO. + versionSonstRechtsgrundlageText: + doc: Textliche Spezifikation einer zugrunde liegenden anderen Rechtsgrundlage + als BauGB / BauNVO. + wirksamkeitsDatum: + doc: Datum der Wirksamkeit + doc: Klasse zur Modellierung eines gesamten Flächennutzungsplans. +FP_PrivilegiertesVorhaben: + attributes: + vorhaben: + doc: Nähere Beschreibung des Vorhabens. + zweckbestimmung: + doc: Zweckbestimmungen des Vorhabens. + doc: Standorte für privilegierte Außenbereichsvorhaben und für sonstige Anlagen + in Außenbereichen gem. § 35 Abs. 1 und 2 BauGB. +FP_Punktobjekt: + attributes: + nordwinkel: + doc: Orientierung des Punktobjektes als Winkel gegen die Nordrichtung. Zählweise + im geographischen Sinn (von Nord über Ost nach Süd und West). + position: + doc: Punktförmiger Raumbezug (Einzelpunkt oder Punktmenge). + doc: Basisklasse für alle Objekte eines Flächennutzungsplans mit punktförmigem Raumbezug + (Einzelpunkt oder Punktmenge). +FP_SchutzPflegeEntwicklung: + attributes: + istAusgleich: + doc: Gibt an, ob die Maßnahme zum Ausgleich eines Eingriffs benutzt wird. + massnahme: + doc: Durchzuführende Maßnahme. + sonstZiel: + doc: Textlich formuliertes Ziel, wenn das Attribut ziel den Wert 9999 (Sonstiges) + hat. + ziel: + doc: Ziel der Maßnahmen + doc: Umgrenzung von Flächen für Maßnahmen zum Schutz, zur Pflege und zur Entwicklung + von Natur und Landschaft (§5 Abs. 2, Nr. 10 BauGB) +FP_SpielSportanlage: + attributes: + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Zweckbestimmung.\r\nDer\ + \ an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + zweckbestimmung: + doc: Zweckbestimmung der Fläche. + doc: Darstellung von Flächen für Spiel- und Sportanlagen nach §5, Abs. 2, Nr. 2 + BauGB. +FP_Strassenverkehr: + attributes: + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Zweckbestimmung.\r\nDer\ + \ an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + nutzungsform: + doc: Nutzungsform + zweckbestimmung: + doc: Zweckbestimmung des Objektes. + doc: Darstellung von Flächen für den überörtlichen Verkehr und für die örtlichen + Hauptverkehrszüge ( §5, Abs. 2, Nr. 3 BauGB). +FP_TextAbschnitt: + attributes: + rechtscharakter: + doc: "Rechtscharakter des textlich formulierten Planinhalts.\r\n" + doc: 'Texlich formulierter Inhalt eines Flächennutzungsplans, der einen anderen + Rechtscharakter als das zugrunde liegende Fachobjekt hat (Attribut ' +FP_TextlicheDarstellungsFlaeche: + attributes: {} + doc: Bereich, in dem bestimmte Textliche Darstellungen gültig sind, die über die + Relation " +FP_Ueberlagerungsobjekt: + attributes: {} + doc: Basisklasse für alle Objekte eines Flächennutzungsplans mit flächenhaftem Raumbezug, + die immer Überlagerungsobjekte sind. +FP_UnverbindlicheVormerkung: + attributes: + vormerkung: + doc: Text der Vormerkung. + doc: "Unverbindliche Vormerkung späterer Planungsabsichten\r\n" +FP_VerEntsorgung: + attributes: + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Zweckbestimmung.\r\nDer\ + \ an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + textlicheErgaenzung: + doc: Textliche Ergänzung der Flächenausweisung. + zugunstenVon: + doc: Angabe des Begünstigten der Ausweisung. + zweckbestimmung: + doc: Zweckbestimmung der Fläche. + doc: Flächen für Versorgungsanlagen, für die Abfallentsorgung und Abwasserbeseitigung + sowie für Ablagerungen (§5, Abs. 2, Nr. 4 BauGB). +FP_VorbehalteFlaeche: + attributes: + vorbehalt: + doc: Textliche Formulierung des Vorbehalts. + doc: Flächen auf denen bestimmte Vorbehalte wirksam sind. +FP_WaldFlaeche: + attributes: + betreten: + doc: Festlegung zusätzlicher, normalerweise nicht-gestatteter Aktivitäten, die + in dem Wald ausgeführt werden dürfen, nach §14 Abs. 2 Bundeswaldgesetz. + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Funktion des Waldes.\r\n\ + Der an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + eigentumsart: + doc: Festlegung der Eigentumsart des Waldes + zweckbestimmung: + doc: Funktion der Waldfläche. + doc: Darstellung von Waldflächen nach §5, Abs. 2, Nr. 9b, +FP_Wasserwirtschaft: + attributes: + detaillierteZweckbestimmung: + doc: "Über eine Codeliste definierte detailliertere Zweckbestimmung des Objektes.\r\ + \nDer an einer bestimmten Listenposition aufgeführte Wert von \"detaillierteZweckbestimmung\"\ + \ bezieht sich auf den an gleicher Position stehenden Attributwert von \"\ + zweckbestimmung\"." + zweckbestimmung: + doc: Zweckbestimmung des Objektes + doc: 'Die für die Wasserwirtschaft vorgesehenen Flächen sowie Flächen, die im Interesse + des Hochwasserschutzes und der Regelung des Wasserabflusses freizuhalten sind + (§5 Abs. 2 Nr. 7 BauGB). ' +FP_ZentralerVersorgungsbereich: + attributes: + auspraegung: + doc: Über eine Codeliste definierte Ausprägung eines zentralen Versorgungsbereiches. + doc: "Darstellung nach § 5 Abs. 2 Nr. 2d (Ausstattung des Gemeindegebietes mit zentralen\ + \ Versorgungsbereichen).\r\n" +LP_Abgrenzung: + attributes: {} + doc: "Abgrenzungen unterschiedlicher Ziel- und Zweckbestimmungen und Nutzungsarten,\ + \ Abgrenzungen unterschiedlicher Biotoptypen.\r\n" +LP_AllgGruenflaeche: + attributes: {} + doc: "Allgemeine Grünflächen.\r\n" +LP_AnpflanzungBindungErhaltung: + attributes: + anzahl: + doc: Anzahl der zu pflanzenden Objekte + gegenstand: + doc: Gegenstand der Maßnahme. + istAusgleich: + doc: Gibt an, ob die Fläche oder Maßnahme zum Ausgleich von Eingriffen genutzt + wird. + kronendurchmesser: + doc: Durchmesser der Baumkrone bei zu erhaltenden Bäumen. + massnahme: + doc: "Art der Maßnahme\r\n" + mindesthoehe: + doc: Mindesthöhe einer Pflanze (z.B. Mindesthöhe einer zu pflanzenden Hecke) + pflanzart: + doc: "Botanische Angabe der zu erhaltenden bzw. der zu pflanzenden Pflanzen.\ + \ \r\n" + pflanztiefe: + doc: Pflanztiefe + doc: Festsetzungen zum Erhalten und Anpflanzen von Bäumen, Sträuchern und sonstigen + Bepflanzungen in einem Planwerk mit landschaftsplanerischen Festsetzungen. Die + Festsetzungen können durch eine Spezifizierung eines Kronendurchmessers (z.B. + für Baumpflanzungen), die Pflanztiefe und Mindesthöhe von Anpflanzungen (z.B. + bei der Anpflanzung von Hecken) oder durch botanische Spezifizierung differenziert + werden. +LP_Ausgleich: + attributes: + massnahme: + doc: Durchzuführende Maßnahme (Textform) + massnahmeKuerzel: + doc: Kürzel der durchzuführenden Maßnahme. + ziel: + doc: Ziel der Maßnahme + doc: Flächen und Maßnahmen zum Ausgleich von Eingriffen im Sinne des § 8 und 8a + BNatSchG (in Verbindung mit § 1a BauGB, Ausgleichs- und Ersatzmaßnahmen). +LP_Bereich: + attributes: + gehoertZuPlan: + doc: Referenz auf den Landschaftsplan, zu dem der Bereich gehört. + doc: "Ein Bereich eines Landschaftsplans.\r\n" +LP_Biotopverbundflaeche: + attributes: {} + doc: "Biotop-Verbundfläche\r\n" +LP_Bodenschutzrecht: + attributes: + detailTyp: + doc: Über eine Codeliste definierter detaillierterer Typ des Schutzobjektes + typ: + doc: Typ des Schutzobjektes + doc: "Gebiete und Gebietsteile mit rechtlichen Bindungen nach anderen Fachgesetzen\ + \ (soweit sie für den Schutz, die Pflege und die Entwicklung von Natur und Landschaft\ + \ bedeutsam sind). Hier: Flächen mit schädlichen Bodenveränderungen nach dem Bodenschutzgesetz.\r\ + \n" +LP_ErholungFreizeit: + attributes: + detaillierteFunktion: + doc: "Über eine Codeliste definierte detailliertere Funktion eines Freizeit-\ + \ oder Erholungs-Objektes.\r\nDer an einer bestimmten Listenposition aufgeführte\ + \ Wert von \"detaillierteFunktion\" bezieht sich auf den an gleicher Position\ + \ stehenden Attributwert von \"funktion\"." + funktion: + doc: Funktion der Fläche. + doc: Sonstige Gebiete, Objekte, Zweckbestimmungen oder Maßnahmen mit besonderen + Funktionen für die landschaftsgebundene Erholung und Freizeit. +LP_Flaechenobjekt: + attributes: + position: + doc: Flächenhafter Raumbezug des Objektes (Eine Einzelfläche oder eine Menge + von Flächen, die sich nicht überlappen dürfen). . + doc: Basisklasse für alle Objekte eines Landschaftsplans mit flächenhaftem Raumbezug + (eine Einzelfläche oder eine Menge von Flächen, die sich nicht überlappen dürfen). +LP_Forstrecht: + attributes: + detailTyp: + doc: Über eine Codeliste definierter detaillierterer Typ des Schutzobjektes. + typ: + doc: Typ des Schutzobjektes. + doc: "Gebiete und Gebietsteile mit rechtlichen Bindungen nach anderen Fachgesetzen\ + \ (soweit sie für den Schutz, die Pflege und die Entwicklung von Natur und Landschaft\ + \ bedeutsam sind). Hier: Schutzgebiete und sonstige Flächen nach dem Bundeswaldgesetz.\r\ + \n" +LP_GenerischesObjekt: + attributes: + zweckbestimmung: + doc: Über eine Codeliste definierte Zweckbestimmung des Generischen Objektes. + doc: Klasse zur Modellierung aller Inhalte des Landschaftsplans, die durch keine + spezifische XPlanung-Klasse repräsentiert werden können. +LP_Geometrieobjekt: + attributes: + flussrichtung: + doc: "Das Attribut ist nur relevant, wenn ein Geometrieobjekt einen linienhaften\ + \ Raumbezug hat. Ist es mit dem Wert true belegt, wird damit ausgedrückt,\ + \ dass der Linie eine Flussrichtung in Digitalisierungsrichtung zugeordnet\ + \ ist. In diesem Fall darf bei Im- und Export die Digitalisierungsreihenfolge\ + \ der Stützpunkte nicht geändert werden. Wie eine definierte Flussrichtung\ + \ zu interpretieren oder bei einer Plandarstellung zu visualisieren ist,\ + \ bleibt der Implementierung überlassen.\r\nIst der Attributwert false oder\ + \ das Attribut nicht belegt, ist die Digitalisierungsreihenfolge der Stützpunkte\ + \ irrelevant." + nordwinkel: + doc: Orientierung des Objektes bei punkförmigem Raumbezug als Winkel gegen die + Nordrichtung. Zählweise im geographischen Sinn (von Nord über Ost nach Süd + und West). + position: + doc: Raumbezug - Entweder punktförmig, linienförmig oder flächenhaft, gemischte + Geometrie ist nicht zugelassen. + doc: Basisklasse für alle Objekte eines Landschaftsplans mit variablem Raumbezug. + Ein konkretes Objekt muss entweder punktförmigen, linienförmigen oder flächenhaften + Raumbezug haben, gemischte Geometrie ist nicht zugelassen. +LP_Landschaftsbild: + attributes: + massnahme: + doc: Über eine Codeliste definierte Spezifizierung der Maßnahme zum Landschaftsbild. + doc: Festlegung, Darstellung bzw. Festsetzung zum Landschaftsbild in einem landschaftsplanerischen + Planwerk. +LP_Linienobjekt: + attributes: + position: + doc: Linienförmiger Raumbezug (Einzelne zusammenhängende Kurve, die aus Linienstücken + und Kreisbögen aufgebaut sit, oder eine Menge derartiger Kurven), + doc: Basisklasse für alle Objekte eines Landschaftsplans mit linienförmigem Raumbezug + (eine einzelne zusammenhängende Kurve, die aus Linienstücken und Kreisbögen zusammengesetzt + sein kann, oder eine Menge derartiger Kurven). +LP_NutzungsAusschluss: + attributes: + auszuschliessendeNutzungen: + doc: Auszuschließende Nutzungen (Textform). + auszuschliessendeNutzungenKuerzel: + doc: Auszuschließende Nutzungen (Kürzel). + begruendung: + doc: Begründung des Ausschlusses (Textform). + begruendungKuerzel: + doc: Begründung des Ausschlusses (Kürzel) + doc: "Flächen und Objekte die bestimmte geplante oder absehbare Nutzungsänderungen\ + \ nicht erfahren sollen.\r\n" +LP_NutzungserfordernisRegelung: + attributes: + erfordernisRegelung: + doc: Nutzungserfordernis oder -Regelung (Textform). + erfordernisRegelungKuerzel: + doc: Nutzungserfordernis oder -Regelung (Kürzel). + regelung: + doc: Nutzungsregelung (Klassifikation). + ziel: + doc: Ziel der Nutzungserfordernis oder Regelung. + doc: Flächen mit Nutzungserfordernissen und Nutzungsregelungen zum Schutz, zur Pflege + und zur Entwicklung von Natur und Landschaft. +LP_Objekt: + attributes: + konkretisierung: + doc: Textliche Konkretisierung der rechtlichen Charakterisierung. + rechtscharakter: + doc: Rechtliche Charakterisierung des Planinhalts. + refTextInhalt: + doc: Referenz eines raumbezogenen Fachobjektes auf textuell formulierte Planinhalte. + doc: Basisklasse für alle spezifischen Inhalte eines Landschaftsplans. +LP_Plan: + attributes: + aenderungenBisDatum: + doc: Datum, bis zum Planänderungen berücksichtigt wurden. + aufstellungsbeschlussDatum: + doc: Datum des Aufstellungsbeschlusses + auslegungsDatum: + doc: Datum der öffentlichen Auslegung. + bereich: + doc: Referenz auf einen Bereich des Landschaftsplans. + bundesland: + doc: Zuständiges Bundesland + entwurfsbeschlussDatum: + doc: Datum des Entwurfsbeschlusses. + inkrafttretenDatum: + doc: Datum des Inkrafttretens. + oeffentlichkeitsbeteiligungDatum: + doc: Datum der Öffentlichkeits-Beteiligung. + planArt: + doc: Typ des vorliegenden Landschaftsplans. + planbeschlussDatum: + doc: Datum des Planbeschlusses. + planungstraeger: + doc: Bezeichnung des Planungsträgers. + planungstraegerGKZ: + doc: Gemeindekennziffer des Planungsträgers. + rechtlicheAussenwirkung: + doc: Gibt an, ob der Plan eine rechtliche Außenwirkung hat. + rechtsstand: + doc: Rechtsstand des Plans + sonstPlanArt: + doc: Spezifikation einer "Sonstigen Planart", wenn kein Plantyp aus der Enumeration + LP_PlanArt zutreffend ist. Das Attribut "planArt" muss in diesem Fall der + Wert 9999 haben. + sonstVerfahrensDatum: + doc: Sonstiges Verfahrens-Datum. + tOeBbeteiligungsDatum: + doc: Datum der Beteiligung der Träger öffentlicher Belange. + doc: Die Klasse modelliert ein Planwerk mit landschaftsplanerischen Festlegungen, + Darstellungen bzw. Festsetzungen. +LP_PlanerischeVertiefung: + attributes: + vertiefung: + doc: Textliche Formulierung der Vertiefung + doc: "Bereiche, die einer planerischen Vertiefung bedürfen.\r\n" +LP_Punktobjekt: + attributes: + nordwinkel: + doc: Orientierung des Punktobjektes als Winkel gegen die Nordrichtung. Zählweise + im geographischen Sinn (von Nord über Ost nach Süd und West). + position: + doc: Punktförmiger Raumbezug (Einzelpunkt oder Punktmenge). + doc: Basisklasse für alle Objekte eines Landschaftsplans mit punktförmigem Raumbezug + (Einzelpunkt oder Punktmenge). +LP_SchutzPflegeEntwicklung: + attributes: + istAusgleich: + doc: Gibt an, ob die Maßnahme zum Ausgleich von Eingriffen genutzt wird. + massnahme: + doc: Durchzuführende Maßnahme (Klassifikation). + massnahmeKuerzel: + doc: Kürzel der durchzuführenden Maßnahme. + massnahmeText: + doc: Durchzuführende Maßnahme (Textform). + ziel: + doc: Ziel der Maßnahme + doc: 'Sonstige Flächen und Maßnahmen zum Schutz, zur Pflege und zur Entwicklung + von Natur und Landschaft, soweit sie nicht durch die Klasse ' +LP_SchutzobjektInternatRecht: + attributes: + eigenname: + doc: Eigennahme des Schutzgebietes. + sonstTyp: + doc: Über eine Codeliste definierter zusätzlicher Typ des Schutzobjektes, wenn + typ == 9999 ist. + typ: + doc: Typ der Schutzgebietes. + doc: "Sonstige Schutzgebiete und Schutzobjekte nach internationalem Recht.\r\n" +LP_SchutzobjektLandesrecht: + attributes: + detailTyp: + doc: Über eine Codeliste definierter Typ des Schutzobjektes. + doc: Sonstige Schutzgebiete und Schutzobjekte nach Landesrecht. +LP_SonstigesRecht: + attributes: + detailTyp: + doc: Über eine Codeliste definierter detaillierterer Typ des Schutzobjektes. + typ: + doc: Typ des Schutzobjektes. + doc: "Gebiete und Gebietsteile mit rechtlichen Bindungen nach anderen Fachgesetzen\ + \ (soweit sie für den Schutz, die Pflege und die Entwicklung von Natur und Landschaft\ + \ bedeutsam sind). Hier: Sonstige Flächen und Gebiete (z.B. nach Jagdrecht).\r\ + \n" +LP_TextAbschnitt: + attributes: + rechtscharakter: + doc: Rechtscharakter des textlich formulierten Planinhalts. + doc: 'Texlich formulierter Inhalt eines Landschaftsplans, der einen anderen Rechtscharakter + als das zugrunde liegende Fachobjekt hat (Attribut ' +LP_TextlicheFestsetzungsFlaeche: + attributes: {} + doc: Bereich, in dem bestimmte textliche Festsetzungen gültig sind, die über die + Relation " +LP_WasserrechtGemeingebrEinschraenkungNaturschutz: + attributes: + detailTyp: + doc: Über eine Codeliste definierter Typ des Schutzobjektes. + doc: "Gebiete und Gebietsteile mit rechtlichen Bindungen nach anderen Fachgesetzen\ + \ (soweit sie für den Schutz, die Pflege und die Entwicklung von Natur und Landschaft\ + \ bedeutsam sind). Hier: Flächen mit Einschränkungen des wasserrechtlichen Gemeingebrauchs\ + \ aus Gründen des Naturschutzes.\r\n" +LP_WasserrechtSchutzgebiet: + attributes: + detailTyp: + doc: Über eine Codeliste definierter detaillierterer Typ des Schutzobjektes. + eigenname: + doc: Eigenname des Schutzgebietes. + typ: + doc: Typ des Schutzobjektes. + doc: Wasserrechtliches Schutzgebiet +LP_WasserrechtSonstige: + attributes: + typ: + doc: Über eine Codeliste definierter Typ des Schutzobjektes. + doc: "Gebiete und Gebietsteile mit rechtlichen Bindungen nach anderen Fachgesetzen\ + \ (soweit sie für den Schutz, die Pflege und die Entwicklung von Natur und Landschaft\ + \ bedeutsam sind). Hier: Sonstige wasserrechtliche Flächen.\r\n" +LP_WasserrechtWirtschaftAbflussHochwSchutz: + attributes: + detailTyp: + doc: Über eine Codeliste definierter detaillierterer Typ des Schutzobjektes. + typ: + doc: Typ des Schutzobjektes. + doc: "Gebiete und Gebietsteile mit rechtlichen Bindungen nach anderen Fachgesetzen\ + \ (soweit sie für den Schutz, die Pflege und die Entwicklung von Natur und Landschaft\ + \ bedeutsam sind). Hier: Flächen für die Wasserwirtschaft, den Hochwasserschutz\ + \ und die Regelung des Wasserabflusses nach dem Wasserhaushaltsgesetz.\r\n" +LP_ZuBegruenendeGrundstueckflaeche: + attributes: + gaertnerischanzulegen: + doc: Angabe in wie weit ein Grünfläche gärtnerisch anzulegen ist. + gruenflaechenFaktor: + doc: Angabe des Verhältnisses zwischen einem Flächenanteil Grün und einer bebauten + Fläche (auch als Biotopflächenfaktor bekannt) + doc: "Zu begrünende Grundstücksfläche\r\n" +LP_Zwischennutzung: + attributes: + bindung: + doc: Beschreibung der Bindung (Textform). + bindungKuerzel: + doc: Beschreibung der Bindung (Kürzel). + ziel: + doc: Ziel der Maßnahme. + doc: Flächen und Maßnahmen mit zeitlich befristeten Bindungen zum Schutz, zur Pflege + und zur Entwicklung von Natur und Landschaft ("Zwischennutzungsvorgaben"). +RP_Achse: + attributes: + typ: + doc: Klassifikation verschiedener Achsen. + doc: Achsen bündeln i.d.R. Verkehrs- und Versorgungsinfrastruktur und enthalten + eine relativ dichte Folge von Siedlungskonzentrationen und Zentralen Orten. +RP_Bereich: + attributes: + gehoertZuPlan: + doc: Relation auf den zugehörigen Plan + geltungsmassstab: + doc: (Rechtlicher) Geltungsmaßstab des Bereichs. + versionBROG: + doc: Bekanntmachungs-Datum der zugrunde liegenden Version des ROG. + versionBROGText: + doc: Titel der zugrunde liegenden Version des Bundesraumordnungsgesetzes. + versionLPLG: + doc: Bekanntmachungs-Datum des zugrunde liegenden Landesplanungsgesetzes. + versionLPLGText: + doc: Titel des zugrunde liegenden Landesplanungsgesetzes. + doc: Die Klasse RP_Bereich modelliert einen Bereich eines Raumordnungsplans. Bereiche + strukturieren Pläne räumlich und inhaltlich. +RP_Bodenschutz: + attributes: + typ: + doc: Klassifikation von Bodenschutztypen. + doc: Maßnahmen, die zum Schutz von Böden und Bodenfunktionen (auch vorsorglich) + unter dem Aspekt des Natur- und Umweltschutzes getroffen werden. +RP_Einzelhandel: + attributes: + typ: + doc: Klassifikation von Einzelhandelstypen. + doc: Einzelhandelsstruktur und -funktionen. +RP_Energieversorgung: + attributes: + primaerenergieTyp: + doc: Klassifikation von der mit der Infrastruktur in Beziehung stehenden Primärenergie. + spannung: + doc: Klassifikation von Spannungen. + typ: + doc: Klassifikation von Energieversorgungs-Einrichtungen. + doc: Infrastruktur zur Energieversorgung. Beinhaltet Energieerzeugung und die Belieferung + von Verbrauchern mit Nutzenergie. Erneuerbare Energie wie Windkraft wird im Normalfall + auf die Klasse RP_ErneuerbareEnergie im Unterpaket Freiraumstruktur zugeordnet. + Je nach Kontext kann aber auch eine Zuordnung auf RP_Energieversorgung stattfinden. +RP_Entsorgung: + attributes: + abfallTyp: + doc: Klassifikation von mit der Entsorgungsinfrastruktur in Beziehung stehenden + Abfalltypen + istAufschuettungAblagerung: + doc: Gibt an, ob die Entsorgung in Form einer Aufschüttung oder Ablagerung erfolgt + typAE: + doc: Klassifikation von Abfallentsorgung-Infrastruktur. + typAW: + doc: Klassifikation von Abwasser-Infrastruktur. + doc: "Entsorgungs-Infrastruktur Beinhaltet Abfallentsorgung und Abwasserentsorgung.\r\ + \n" +RP_Erholung: + attributes: + besondererTyp: + doc: Klassifikation von besonderen Typen für Tourismus und/oder Erholung. + typErholung: + doc: Klassifikation von Erholungstypen. + typTourismus: + doc: Klassifikation von Tourismustypen. + doc: Freizeit, Erholung und Tourismus. +RP_ErneuerbareEnergie: + attributes: + typ: + doc: Klassifikation von Typen Erneuerbarer Energie. + doc: "Erneuerbare Energie inklusive Windenergienutzung.\r\n" +RP_Forstwirtschaft: + attributes: + typ: + doc: Klassifikation von Forstwirtschaftstypen und Wäldern. + doc: "Forstwirtschaft ist die zielgerichtete Bewirtschaftung von Wäldern.\r\n" +RP_Freiraum: + attributes: + imVerbund: + doc: Zeigt an, ob das Objekt in einem (Freiraum-)Verbund liegt. + istAusgleichsgebiet: + doc: Zeigt an, ob das Objekt ein Ausgleichsgebiet ist. + doc: "Allgemeines Freiraumobjekt.\r\n" +RP_Funktionszuweisung: + attributes: + bezeichnung: + doc: Bezeichnung und/oder Erörterung einer Gebietsfunktion. + typ: + doc: Klassifikation des Gebietes nach Bundesraumordnungsgesetz. + doc: Gebiets- und Gemeindefunktionen. +RP_GenerischesObjekt: + attributes: + typ: + doc: Über eine CodeList definierte Zweckbestimmung der Festlegung. + doc: Klasse zur Modellierung aller Inhalte des Raumordnungsplans, die durch keine + andere Klasse des RPlan-Fachschemas dargestellt werden können. +RP_Geometrieobjekt: + attributes: + flaechenschluss: + doc: Zeigt an, ob für das Objekt Flächenschluss vorliegt. + flussrichtung: + doc: "Das Attribut ist nur relevant, wenn ein Geometrieobjekt einen linienhaften\ + \ Raumbezug hat. Ist es mit dem Wert true belegt, wird damit ausgedrückt,\ + \ dass der Linie eine Flussrichtung in Digitalisierungsrichtung zugeordnet\ + \ ist. In diesem Fall darf bei Im- und Export die Digitalisierungsreihenfolge\ + \ der Stützpunkte nicht geändert werden. Wie eine definierte Flussrichtung\ + \ zu interpretieren oder bei einer Plandarstellung zu visualisieren ist,\ + \ bleibt der Implementierung überlassen.\r\nIst der Attributwert false oder\ + \ das Attribut nicht belegt, ist die Digitalisierungsreihenfolge der Stützpunkte\ + \ irrelevant." + nordwinkel: + doc: Orientierung des Objektes bei punktförmigem Raumbezug als Winkel gegen + die Nordrichtung. Zählweise im geographischen Sinn (von Nord über Ost nach + Süd und West). + position: + doc: Variabler Raumbezug. + doc: Basisklasse für alle Objekte eines Raumordnungsplans mit variablem Raumbezug. + Ein konkretes Objekt muss entweder punktförmigen, linienförmigen oder flächenhaften + Raumbezug haben, gemischte Geometrie ist nicht zugelassen. RP_Geometrieobjekt + selbst ist abstrakt. +RP_Gewaesser: + attributes: + gewaesserTyp: + doc: Spezifiziert den Typ des Gewässers. + doc: Gewässer, die nicht andersweitig erfasst werden, zum Beispiel Flüsse oder Seen. +RP_Grenze: + attributes: + sonstTyp: + doc: Erweiterter Typ. + spezifischerTyp: + doc: Spezifischer Typ der Grenze + typ: + doc: Typ der Grenze + doc: Grenzen. +RP_GruenzugGruenzaesur: + attributes: + typ: + doc: Klassifikation von Zäsurtypen. + doc: Grünzüge und kleinräumigere Grünzäsuren sind Ordnungsinstrumente zur Freiraumsicherung. + Teilweise werden Grünzüge auch Trenngrün genannt. +RP_Hochwasserschutz: + attributes: + typ: + doc: Klassifikation von Hochwasserschutztypen. + doc: "Die Klasse RP_Hochwasserschutz enthält Hochwasserschutz und vorbeugenden Hochwasserschutz.\r\ + \n" +RP_IndustrieGewerbe: + attributes: + typ: + doc: Klassifikation von Industrie- und Gewerbetypen. + doc: Industrie- und Gewerbestrukturen und -funktionen. +RP_Klimaschutz: + attributes: + typ: + doc: Klassifikation von Lufttypen. + doc: (Siedlungs-) Klimaschutz. Beinhaltet zum Beispiel auch Kalt- und Frischluftschneisen. +RP_Kommunikation: + attributes: + typ: + doc: Klassifikation von Kommunikations-Infrastruktur. + doc: Infrastruktur zur Telekommunikation, digitale Infrastruktur oder Kommunikationsinfrastruktur. +RP_Kulturlandschaft: + attributes: + typ: + doc: Klassifikation von Kulturlandschaftstypen. + doc: "Landschaftsbereich mit überwiegend anthropogenen Ökosystemen (historisch geprägt\ + \ und gewachsen). Sie sind nach §2, Nr. 5 des ROG mit ihren Kultur- und Naturdenkmälern\ + \ zu erhalten und zu entwickeln. \r\n" +RP_LaermschutzBauschutz: + attributes: + typ: + doc: Klassifikation von Lärmschutztypen. + doc: Infrastruktur zum Lärmschutz und/oder Bauschutz. +RP_Landwirtschaft: + attributes: + typ: + doc: Klassifikation von Landwirtschaftstypen. + doc: Landwirtschaft, hauptsächlich im ländlichen Raum angesiedelt, erfüllt für die + Gesellschaft wichtige Funktionen in der Produktion- und Versorgung mit Lebensmitteln, + für Freizeit und Freiraum oder zur Biodiversität. +RP_Legendenobjekt: + attributes: + gehoertZuPraesentationsobjekt: + doc: Verweis auf das zugehörige Präsentationsobjekt + legendenBezeichnung: + doc: Bezeichnung des XPlan-FeatureTypes in der Legende des dazugehörigen Plans. + reflegendenBild: + doc: Referenz auf das Bild eines Planzeichens in der Legende eines Plans. + doc: Die Klasse RP_Legendenobjekt enthält Daten zur Legende und Darstellung im Ursprungsplan. +RP_Luftverkehr: + attributes: + typ: + doc: Klassifikation von Luftverkehr-Infrastruktur. + doc: Luftverkehr-Infrastruktur ist Infrastruktur, die im Zusammenhang mit der Beförderung + von Personen, Gepäck, Fracht und Post mit staatlich zugelassenen Luftfahrzeugen, + besonders Flugzeugen steht. +RP_NaturLandschaft: + attributes: + typ: + doc: Klassifikation von Naturschutz, Landschaftsschutz und Naturlandschafttypen. + doc: Naturlandschaften sind von umitellbaren menschlichen Aktivitäten weitestgehend + unbeeinflusst gebliebene Landschaft. +RP_NaturschutzrechtlichesSchutzgebiet: + attributes: + istKernzone: + doc: Gibt an, ob es sich um eine Kernzone handelt. + typ: + doc: Klassifikation des Naturschutzgebietes. + doc: Schutzgebiet nach Bundes-Naturschutzgesetz. +RP_Objekt: + attributes: + bedeutsamkeit: + doc: Bedeutsamkeit eines Objekts. + gebietsTyp: + doc: Gebietstyp eines Objekts. + istZweckbindung: + doc: Zeigt an, ob es sich bei diesem Objekt um eine Zweckbindung handelt. + konkretisierung: + doc: Konkretisierung des Rechtscharakters. + kuestenmeer: + doc: Zeigt an, ob das Objekt im Küstenmeer liegt. + rechtscharakter: + doc: Rechtscharakter des Planinhalts. + refTextInhalt: + doc: Referenz eines raumbezogenen Fachobjektes auf textuell formulierte Planinhalte. + doc: RP_Objekt ist die Basisklasse für alle spezifischen Festlegungen eines Raumordnungsplans. + Sie selbst ist abstrakt, d.h. sie wird selbst nicht als eigenes Objekt verwendet, + sondern vererbt nur ihre Attribute an spezielle Klassen. +RP_Plan: + attributes: + aenderungenBisDatum: + doc: Datum, bis zu dem Planänderungen berücksichtigt wurden. + amtlicherSchluessel: + doc: Amtlicher Schlüssel eines Plans auf Basis des AGS-Schlüssels (Amtlicher + Gemeindeschlüssel). + aufstellungsbeschlussDatum: + doc: Datum des Aufstellungsbeschlusses. + auslegungEndDatum: + doc: End-Datum der öffentlichen Auslegung. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + auslegungStartDatum: + doc: Start-Datum der öffentlichen Auslegung. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + bereich: + doc: Relation auf einen Bereich + bundesland: + doc: Zuständige Bundesländer für den Plan. + datumDesInkrafttretens: + doc: Datum des Inkrafttretens des Plans. + entwurfsbeschlussDatum: + doc: Datum des Entwurfsbeschlusses + genehmigungsbehoerde: + doc: Zuständige Genehmigungsbehörde + planArt: + doc: Art des Raumordnungsplans. + planbeschlussDatum: + doc: Datum des Planbeschlusses. + planungsregion: + doc: Kennziffer der Planungsregion. + rechtsstand: + doc: Rechtsstand des Plans. + sonstPlanArt: + doc: Spezifikation einer weiteren Planart (CodeList) bei planArt == 9999. + status: + doc: Status des Plans, definiert über eine CodeList. + teilabschnitt: + doc: Kennziffer des Teilabschnittes. + traegerbeteiligungsEndDatum: + doc: End-Datum der Trägerbeteiligung. Bei mehrfacher Trägerbeteiligung können + mehrere Datumsangaben spezifiziert werden. + traegerbeteiligungsStartDatum: + doc: Start-Datum der Trägerbeteiligung. Bei mehrfacher Trägerbeteiligung können + mehrere Datumsangaben spezifiziert werden. + verfahren: + doc: Verfahrensstatus des Plans. + doc: Die Klasse RP_Plan modelliert einen Raumordnungsplan. +RP_Planungsraum: + attributes: + planungsraumBeschreibung: + doc: Textliche Beschreibung eines Planungsrauminhalts. + doc: Modelliert einen allgemeinen Planungsraum. +RP_RadwegWanderweg: + attributes: + typ: + doc: Klassifikation von Radwegen und Wanderwegen. + doc: Radwege und Wanderwege. Straßenbegleitend oder selbstständig geführt. +RP_Raumkategorie: + attributes: + besondererTyp: + doc: Klassifikation verschiedener besonderer Raumkategorien. + typ: + doc: Klassifikation verschiedener Raumkategorien. + doc: Raumkategorien sind nach bestimmten Kriterien abgegrenze Gebiete, in denen + vergleichbare Strukturen bestehen und in denen die Raumordnung gleichartige Ziele + verfolgt. Kriterien können z.B. siedlungsstrukturell, qualitativ oder potentialorientiert + sein. +RP_Rohstoff: + attributes: + bergbauplanungTyp: + doc: Klassifikation von Bergbauplanungstypen. + detaillierterRohstoffTyp: + doc: Abgebauer Rohstoff in Textform + folgenutzung: + doc: Klassifikation von Folgenutzungen bestimmter bergbaulicher Maßnahmen. + folgenutzungText: + doc: Textliche Festlegungen und Spezifizierungen zur Folgenutzung einer Bergbauplanung. + istAufschuettungAblagerung: + doc: Gibt an, ob der Rohstoff aus einer Aufschüttung oder Ablagerung gewonnen + wird + rohstoffTyp: + doc: Abgebauter Rohstoff. + tiefe: + doc: Tiefe eines Rohstoffes + zeitstufe: + doc: Zeitstufe des Rohstoffabbaus. + zeitstufeText: + doc: Textliche Spezifizierung einer Rohstoffzeitstufe, zum Beispiel kurzfristiger + Abbau (Zeitstufe I) und langfristige Sicherung für mindestens 25-30 Jahre + (Zeitstufe II). + doc: Rohstoff, inklusive Rohstoffprospektion, Rohstoffsicherung, Rohstoffabbau, + Bergbau und Bergbaufolgelandschaft. +RP_Schienenverkehr: + attributes: + besondererTyp: + doc: Klassifikation von besonderer Schienenverkehr-Infrastruktur. + typ: + doc: Klassifikation von Schienenverkehr-Infrastruktur. + doc: Schienenverkehr-Infrastruktur. +RP_Siedlung: + attributes: + bauhoehenbeschraenkung: + doc: Assoziierte Bauhöhenbeschränkung. + istSiedlungsbeschraenkung: + doc: Abfrage, ob der FeatureType eine Siedlungsbeschränkung ist. + doc: Allgemeines Siedlungsobjekt. Dieses vererbt an mehrere Spezialisierungen, ist + aber selbst nicht abstrakt. +RP_SonstVerkehr: + attributes: + typ: + doc: Sonstige Klassifikation von Verkehrs-Infrastruktur. + doc: Sonstige Verkehrsinfrastruktur, die sich nicht eindeutig einem anderen Typ + zuordnen lässt. +RP_SonstigeInfrastruktur: + attributes: {} + doc: Sonstige Infrastruktur. +RP_SonstigerFreiraumschutz: + attributes: {} + doc: Sonstiger Freiraumschutz. Nicht anderweitig zuzuordnende Freiraumstrukturen. +RP_SonstigerSiedlungsbereich: + attributes: {} + doc: Sonstiger Siedlungsbereich. +RP_SozialeInfrastruktur: + attributes: + typ: + doc: Klassifikation von Sozialer Infrastruktur. + doc: Soziale Infrastruktur ist ein (unpräziser) Sammelbegriff für Einrichtungen, + Leistungen und Dienste in den Kommunen, distinkt von Verkehr, Energieversorgung + und Entsorgung. Sie umfasst u.a. Bildung, Gesundheit, Sozialeinrichtungen, Kultureinrichtungen + und Einrichtungen der öffentlichen Verwaltung. +RP_Sperrgebiet: + attributes: + typ: + doc: Klassifikation verschiedener Sperrgebiettypen. + doc: Sperrgebiet, Gelände oder Areal, das für die Zivilbevölkerung überhaupt nicht + oder zeitweise nicht zugänglich ist. +RP_Sportanlage: + attributes: + typ: + doc: Klassifikation von Sportanlagen. + doc: "Sportanlagen und -bereiche.\r\n" +RP_Strassenverkehr: + attributes: + besondererTyp: + doc: Klassifikation von besonderer Strassenverkehr-Infrastruktur. + typ: + doc: Klassifikation von Strassenverkehr-Infrastruktur. + doc: Strassenverkehr-Infrastruktur. +RP_TextAbschnitt: + attributes: + rechtscharakter: + doc: "Rechtscharakter des textlich formulierten Planinhalts.\r\n" + doc: 'Texlich formulierter Inhalt eines Raumordnungsplans, der einen anderen Rechtscharakter + als das zugrunde liegende Fachobjekt hat (Attribut ' +RP_Verkehr: + attributes: + allgemeinerTyp: + doc: Allgemeine Klassifikation der Verkehrs-Arten. + bezeichnung: + doc: Bezeichnung eines Verkehrstyps. + status: + doc: Klassifikation von Verkehrsstati. + doc: Enthält allgemeine Verkehrs-Infrastruktur, die auch multiple Typen (etwa Straße + und Schiene) beinhalten kann. Die Klasse selbst vererbt an spezialisierte Verkehrsarten, + ist aber nicht abstrakt (d.h. sie kann selbst auch verwendet werden). +RP_Wasserschutz: + attributes: + typ: + doc: Klassifikation des Wasserschutztyps. + zone: + doc: Wasserschutzzone + doc: Grund-, Trink- und Oberflächenwasserschutz. +RP_Wasserverkehr: + attributes: + typ: + doc: Klassifikation von Wasserverkehr-Infrastruktur. + doc: Wasserverkehr-Infrastruktur. +RP_Wasserwirtschaft: + attributes: + typ: + doc: Klassifikation von Anlagen und Einrichtungen der Wasserwirtschaft + doc: Wasserwirtschaft beinhaltet die zielbewusste Ordnung aller menschlichen Einwirkungen + auf das ober- und unterirdische Wasser. Im Datenschema werden Gewässer, Wasserschutz, + Hochwasserschutz und Wasserverkehr in gesonderten Klassen gehalten. +RP_WohnenSiedlung: + attributes: + typ: + doc: Klassifikation von Wohnen- und Siedlungstypen. + doc: Wohn- und Siedlungsstruktur und -funktionen. +RP_ZentralerOrt: + attributes: + sonstigerTyp: + doc: Sonstige Klassifikation von Zentralen Orten. + typ: + doc: Klassifikation von Zentralen Orten. + doc: Zentrale Orte übernehmen die Versorgung ihrer Einwohner sowie Versorgungs und + Entwicklungsfunktionen für den Einzugsbereich des Zentralen Ortes. Das zentralörtliche + System ist hierarchisch gegliedert. +SO_Bauverbotszone: + attributes: + artDerFestlegung: + doc: Klassifizierung des Bauverbots bzw. der Baubeschränkung + detailArtDerFestlegung: + doc: Detaillierte Klassifizierung des Bauverbots bzw. der Baubeschränkung über + eine Codeliste + name: + doc: Informelle bezeichnung der Festlegung + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung + rechtlicheGrundlage: + doc: Rechtliche Grundlage des Bauverbots bzw. der Baubeschränkung + doc: Bereich, in denen Verbote oder Beschränkungen für die Errichtung baulicher + Anlagen bestehen +SO_Bereich: + attributes: + gehoertZuPlan: + doc: Referenz auf den Plan, zu dem der Bereich gehört + doc: "Bereich eines sonstigen raumbezogenen Plans.\r\n" +SO_Bodenschutzrecht: + attributes: + artDerFestlegung: + doc: Klassifizierung der Festlegung. + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere Klassifizierung der Festlegung. + istVerdachtsflaeche: + doc: Angabe ob es sich um eine Verdachtsfläche handelt. + name: + doc: Informelle Bezeichnung der Festlegung. + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung. bzw. Nummer in einem + Altlast-Kataster + doc: "Festlegung nach Bodenschutzrecht.\r\n" +SO_Denkmalschutzrecht: + attributes: + artDerFestlegung: + doc: Rechtliche Klassifizierung der Festlegung + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere rechtliche Klassifizierung + der Festlegung. + name: + doc: Informelle Bezeichnung der Festlegung + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung. + weltkulturerbe: + doc: Gibt an, ob das geschützte Objekt zum Weltkulturerbe gehört. + doc: "Festlegung nach Denkmalschutzrecht\r\n" +SO_Flaechenobjekt: + attributes: + flaechenschluss: + doc: 'Zeigt an, ob das Objekt als Flächenschlussobjekt oder Überlagerungsobjekt + gebildet werden soll. Flächenschlussobjekte dürfen sich nicht überlappen, + sondern nur an den Flächenrändern berühren, wobei die jeweiligen Stützpunkte + der Randkurven übereinander liegen müssen. Die Vereinigung der Flächenschlussobjekte + überdeckt den Geltungsbereich des sonstigen raumbezogenen Plans vollständig. ' + position: + doc: 'Flächenhafter Raumbezug des Objektes (Eine Einzelfläche oder eine Menge + von Flächen, die sich nicht überlappen dürfen). ' + doc: Basisklasse für alle Objekte mit flächenhaftem Raumbezug (eine Einzelfläche + oder eine Menge von Flächen, die sich nicht überlappen dürfen). +SO_Forstrecht: + attributes: + artDerFestlegung: + doc: Klassifizierung der Eigentumsart des Waldes. + betreten: + doc: Festlegung zusätzlicher, normalerweise nicht-gestatteter Aktivitäten, die + in dem Wald ausgeführt werden dürfen, nach §14 Abs. 2 Bundeswaldgesetz. + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere Klassifizierung der Eigentumsart + des Waldes + funktion: + doc: Klassifizierung der Fukktion des Waldes + name: + doc: Informelle Bezeichnung der Festlegung + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung + doc: Festlegung nach Forstrecht +SO_Gebiet: + attributes: + aufstellungsbeschhlussDatum: + doc: Datum des Aufstellungsbeschlusses + durchfuehrungEndDatum: + doc: End-Datum der Durchführung + durchfuehrungStartDatum: + doc: Start-Datum der Durchführung + gebietsArt: + doc: Klassifikation des Gebietes nach BauGB. + gemeinde: + doc: Zuständige Gemeinde + rechtsstandGebiet: + doc: Rechtsstand der Gebietsausweisung + sonstGebietsArt: + doc: Über eine Codeliste definierte Klassifikation einer nicht auf dem BauGB + beruhenden, z.B. länderspezifischen Gebietsausweisung. In dem Fall muss das + Attribut "gebietsArt" den Wert 9999 (Sonstiges) haben. + sonstRechtsstandGebiet: + doc: Über eine Codeliste definierter sonstiger Rechtsstand der Gebietsausweisung, + der nicht durch die Liste SO_RechtsstandGebietTyp wiedergegeben werden kann. + Das Attribut "rechtsstandGebiet" muss in diesem Fall den Wert 9999 (Sonstiges) + haben. + traegerMassnahme: + doc: Maßnahmen-Träger + doc: "Umgrenzung eines sonstigen Gebietes nach BauGB\r\n" +SO_Gelaendemorphologie: + attributes: + artDerFestlegung: + doc: Klassifikation der Geländestruktur + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere Klassifikation der Geländestruktur + name: + doc: Informelle Bezeichnung der Struktur + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Struktur + doc: Das Landschaftsbild prägende Geländestruktur +SO_Geometrieobjekt: + attributes: + flaechenschluss: + doc: 'Zeigt bei flächenhaftem Raumbezug an, ob das Objekt als Flächenschlussobjekt + oder Überlagerungsobjekt gebildet werden soll. Flächenschlussobjekte dürfen + sich nicht überlappen, sondern nur an den Flächenrändern berühren, wobei die + jeweiligen Stützpunkte der Randkurven übereinander liegen müssen. Die Vereinigung + der Flächenschlussobjekte überdeckt den Geltungsbereich des sonstigen raumbezogenen + Plans vollständig. ' + flussrichtung: + doc: "Das Attribut ist nur relevant, wenn ein Geometrieobjekt einen linienhaften\ + \ Raumbezug hat. Ist es mit dem Wert true belegt, wird damit ausgedrückt,\ + \ dass der Linie eine Flussrichtung in Digitalisierungsrichtung zugeordnet\ + \ ist. In diesem Fall darf bei Im- und Export die Digitalisierungsreihenfolge\ + \ der Stützpunkte nicht geändert werden. Wie eine definierte Flussrichtung\ + \ zu interpretieren oder bei einer Plandarstellung zu visualisieren ist,\ + \ bleibt der Implementierung überlassen.\r\nIst der Attributwert false oder\ + \ das Attribut nicht belegt, ist die Digitalisierungsreihenfolge der Stützpunkte\ + \ irrelevant." + nordwinkel: + doc: Orientierung des Objektes bei punktförmigem Raumbezug als Winkel gegen + die Nordrichtung. Zählweise im geographischen Sinn (von Nord über Ost nach + Süd und West) + position: + doc: Raumbezug - Entweder punktförmig, linienförmig oder flächenhaft, gemischte + Geometrie ist nicht zugelassen. + doc: Basisklasse für alle Objekte mit variablem Raumbezug. Ein konkretes Objekt + muss entweder punktförmigen, linienförmigen oder flächenhaften Raumbezug haben, + gemischte Geometrie ist nicht zugelassen. +SO_Gewaesser: + attributes: + artDerFestlegung: + doc: Klassifizierung des Gewässers + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere Klassifizierung des Gewässers + name: + doc: Informelle Bezeichnung dees Gewässers + nummer: + doc: Amtliche Bezeichnung / Kennziffer des Gewässers. + doc: Abbildung eines bestehenden Gewässers +SO_Grenze: + attributes: + sonstTyp: + doc: Über eine Codeliste definierte weitere Grenztypen, wenn das Attribut typ + den Wert 9999 hat. + typ: + doc: Typ der Grenze + doc: Grenze einer Verwaltungseinheit oder sonstige Grenze in raumbezogenen Plänen. +SO_Linienobjekt: + attributes: + position: + doc: Linienförmiger Raumbezug (Einzelne zusammenhängende Kurve, die aus Linienstücken + und Kreisbögen aufgebaut ist, oder eine Menge derartiger Kurven), + doc: Basisklasse für Objekte mit linienförmigem Raumbezug (eine einzelne zusammenhängende + Kurve, die aus Linienstücken und Kreisbögen zusammengesetzt sein kann, oder eine + Menge derartiger Kurven). +SO_Luftverkehrsrecht: + attributes: + artDerFestlegung: + doc: Rechtliche Klassifizierung der Festlegung. + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere Klassifizierung der Festlegung. + laermschutzzone: + doc: Lärmschutzzone nach LuftVG. + name: + doc: Informelle Bezeichnung der Festlegung + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung + doc: "Festlegung nach Luftverkehrsrecht.\r\n" +SO_Objekt: + attributes: + rechtscharakter: + doc: Rechtscharakter des Planinhalts. + refTextInhalt: + doc: Referenz eines raumbezogenen Fachobjektes auf textuell formulierte Planinhalte. + sonstRechtscharakter: + doc: Klassifizierung des Rechtscharakters wenn das Attribut "rechtscharakter" + den Wert "Sonstiges" (9999) hat. + doc: Basisklasse für die Inhalte sonstiger raumbezogener Planwerke sowie von Klassen + zur Modellierung nachrichtlicher Übernahmen. +SO_Plan: + attributes: + bereich: + doc: Referenz auf einen Bereich des sonstigen raumbezogenen Plans. + gemeinde: + doc: Zuständige Gemeinde + planArt: + doc: Über eine Codeliste definierter Typ des Plans. + planaufstellendeGemeinde: + doc: Die für die ursprüngliche Planaufstellung zuständige Gemeinde, falls diese + nicht unter dem Attribut gemeinde aufgeführt ist. Dies kann z.B. nach Gemeindefusionen + der Fall sein. + plangeber: + doc: "Für den Plan zuständige Stelle.\r\n" + versionBauGBDatum: + doc: Bekanntmachungs-Datum der zugrunde liegenden Version des BauGB. + versionBauGBText: + doc: Textliche Spezifikation der zugrunde liegenden Version des BauGB. + versionSonstRechtsgrundlageDatum: + doc: Bekanntmachungs-Datum einer zugrunde liegenden anderen Rechtsgrundlage + als das BauGB. + versionSonstRechtsgrundlageText: + doc: Textliche Spezifikation einer zugrunde liegenden anderen Rechtsgrundlage + als das BauGB. + doc: Klasse für sonstige, z. B. länderspezifische raumbezogene Planwerke. +SO_Punktobjekt: + attributes: + nordwinkel: + doc: Orientierung des Punktobjektes als Winkel gegen die Nordrichtung. Zählweise + im geographischen Sinn (von Nord über Ost nach Süd und West). + position: + doc: Punktförmiger Raumbezug (Einzelpunkt oder Punktmenge). + doc: Basisklasse für Objekte mit punktförmigem Raumbezug (Einzelpunkt oder Punktmenge). +SO_Schienenverkehrsrecht: + attributes: + artDerFestlegung: + doc: Rechtliche Klassifizierung der Festlegung + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere rechtliche Klassifizierung + der Festlegung. + name: + doc: Informelle Bezeichnung der Festlegung. + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung. + doc: Festlegung nach Schienenverkehrsrecht. +SO_SchutzgebietNaturschutzrecht: + attributes: + artDerFestlegung: + doc: Klassifizierung des Schutzgebietes + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere Klassifizierung + name: + doc: Informeller Name des Schutzgebiets + nummer: + doc: Amtlicher Name / Kennziffer des Gebiets. + zone: + doc: Klassifizierung der Schutzzone + doc: Schutzgebiet nach Naturschutzrecht. +SO_SchutzgebietSonstigesRecht: + attributes: + artDerFestlegung: + doc: Klassifizierung des Schutzgebietes oder Schutzbereichs. + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere Klassifizierung. + name: + doc: Informelle Bezeichnung des Gebiets + nummer: + doc: Amtliche Bezeichnung / Kennziffer des Gebiets + doc: "Sonstige Schutzgebiete nach unterschiedlichen rechtlichen Bestimmungen.\r\n" +SO_SchutzgebietWasserrecht: + attributes: + artDerFestlegung: + doc: Klassifizierung des Schutzgebietes + detailArtDerFestlegung: + doc: Über eine Codelieste definierte detailliertere Klassifizierung + name: + doc: Informelle Bezeichnung des Gebiets + nummer: + doc: Amtliche Bezeichnung / Kennziffer des Gebiets. + zone: + doc: Klassifizierung der Schutzzone + doc: "Schutzgebiet nach WasserSchutzGesetz (WSG) bzw. HeilQuellenSchutzGesetz (HQSG).\r\ + \n" +SO_SonstigesRecht: + attributes: + artDerFestlegung: + doc: Rechtliche Klassifizierung der Festlegung + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere rechtliche Klassifizierung + der Festlegung. + name: + doc: Informelle Bezeichnung der Festlegung + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung + doc: "Sonstige Festlegung.\r\n" +SO_Strassenverkehrsrecht: + attributes: + artDerFestlegung: + doc: Rechtliche Klassifizierung der Festlegung + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere rechtliche Klassifizierung + der Festlegung. + name: + doc: Informelle Bezeichnung der Festlegung. + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung. + doc: "Festlegung nach Straßenverkehrsrecht.\r\n" +SO_TextAbschnitt: + attributes: + rechtscharakter: + doc: "Rechtscharakter des textlich formulierten Planinhalts.\r\n" + doc: 'Textlich formulierter Inhalt eines Sonstigen Plans, der einen anderen Rechtscharakter + als das zugrunde liegende Fachobjekt hat (Attribut ' +SO_Wasserrecht: + attributes: + artDerFestlegung: + doc: Rechtliche Klassifizierung der Festlegung. + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere rechtliche Klassifizierung + der Festlegung. + istNatuerlichesUberschwemmungsgebiet: + doc: Gibt an, ob es sich bei der Fläche um ein natürliches Überschwemmungsgebiet + handelt. + name: + doc: Informelle Bezeichnung der Festlegung + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung + doc: Festlegung nach Wasserhaushaltsgesetz (WHG) +XP_AbstraktesPraesentationsobjekt: + attributes: + art: + doc: "\"art\" gibt die Namen der Attribute an, die mit dem Präsentationsobjekt\ + \ dargestellt werden sollen. Dabei ist beim Verweis auf komplexe Attribute\ + \ des Fachobjekts die Xpath-Syntax zu verwenden. Wenn das zugehörige Attribut\ + \ oder Sub-Attribut des Fachobjekts mehrfach belegt ist, sollte die []-Syntax\ + \ zur Spezifikation des zugehörigen Instanz-Attributs benutzt werden. \r\n\ + \r\nDas Attribut 'art' darf nur bei \"Freien Präsentationsobjekten\" (dientZurDarstellungVon\ + \ = NULL) nicht belegt sein." + darstellungsprioritaet: + doc: Enthält die Darstellungspriorität für Elemente der Signatur. Eine vom Standardwert + abweichende Priorität wird über dieses Attribut definiert und nicht über eine + neue Signatur. + dientZurDarstellungVon: + doc: Verweis auf das Fachobjekt, deren Plandarstellung durch das Präsentationsobjekt + unterstützt werden soll. + gehoertZuBereich: + doc: Referenz auf den Bereich, zu dem das Präsentationsobjekt gehört. + index: + doc: "Wenn das Attribut, das vom Inhalt des Attributs \"art“ bezeichnet wird,\ + \ im Fachobjekt mehrfach belegt ist gibt \"index\" an, auf welche Instanz\ + \ des Attributs sich das Präsentationsobjekt bezieht. Indexnummern beginnen\ + \ dabei immer mit 0.\r\n\r\nDies Attribut ist als \"veraltet\" gekennzeichnet\ + \ und wird in Version 6.0 voraussichtlich wegfallen. Alternativ sollte im\ + \ Attribut \"art\" die XPath-Syntax benutzt werden." + stylesheetId: + doc: Das Attribut "stylesheetId" zeigt auf ein extern definierte Stylesheet, + das Parameter zur Visualisierung von Flächen, Linien, Punkten und Texten enthält. + Jedem Stylesheet ist weiterhin eine Darstellungspriorität zugeordnet. Außerdem + kann ein Stylesheet logische Elemente enthalten, die die Visualisierung abhängig + machen vom Wert des durch "art" definierten Attributes des Fachobjektes, das + durch die Relation "dientZurDarstellungVon" referiert wird. + doc: 'Abstrakte Basisklasse für alle Präsentationsobjekte. Die Attribute entsprechen + dem ALKIS-Objekt AP_GPO, wobei das Attribut "signaturnummer" in ' +XP_BegruendungAbschnitt: + attributes: + refText: + doc: Referenz auf ein externes Dokument das den Begründungs-Abschnitt enthält. + schluessel: + doc: Schlüssel zur Referenzierung des Abschnitts von einem Fachobjekt aus. + text: + doc: Inhalt eines Abschnitts der Begründung. + doc: Ein Abschnitt der Begründung des Plans. +XP_Bereich: + attributes: + bedeutung: + doc: Spezifikation der semantischen Bedeutung eines Bereiches. + detaillierteBedeutung: + doc: Detaillierte Erklärung der semantischen Bedeutung eines Bereiches, in Ergänzung + des Attributs "bedeutung". + erstellungsMassstab: + doc: Der bei der Erstellung der Inhalte des Bereichs benutzte Kartenmaßstab. + Wenn dieses Attribut nicht spezifiziert ist, gilt für den Bereich der auf + Planebene (XP_Plan) spezifizierte Maßstab. + geltungsbereich: + doc: Räumliche Abgrenzung des Bereiches. Wenn dieses Attribut nicht spezifiziert + ist, gilt für den Bereich der auf Planebene (XP_Plan) spezifizierte Geltungsbereich. + name: + doc: Bezeichnung des Bereiches + nummer: + doc: Nummer des Bereichs + planinhalt: + doc: Verweis auf einen Planinhalt des Bereichs + praesentationsobjekt: + doc: Referenz auf ein Präsentationsbereich, das zum Bereich gehört. + rasterBasis: + doc: "Referenz auf einen georeferenzierte Rasterplan, der die Inhalte des Bereichs\ + \ wiedergibt.\r\n\r\nDiese Relation ist veraltet und wird in XPlanGML 6.0\ + \ wegfallen. XP_Rasterdarstellung sollte folgendermaßen abgebildet werden:\r\ + \n\r\nXP_Rasterdarstellung.refScan --> XP_Bereich.refScan\r\nXP_Rasterdarstellung.refText\ + \ --> XP_Plan.texte\r\nXP_Rasterdarstellung.refLegende --> XP_Plan.externeReferenz\r\ + \n" + refScan: + doc: "Referenz auf einen georeferenzierteRasterplan, der die Inhalte des Bereichs + wiedergibt. Das über refScan referierte Rasterbild zeigt einen Plan, dessen Geltungsbereich durch den + Geltungsbereich des Bereiches (Attribut geltungsbereich von XP_Bereich) oder, wenn geltungsbereich nicht + belegt ist, den Geltungsbereich des Gesamtplans (Attribut raeumlicherGeltungsbereich von XP_Plan) definiert ist. + Im Standard sind nur georeferenzierte Rasterpläne zugelassen. Die über refScan referierte externe Referenz + muss deshalb entweder von der Art \"PlanMitGeoreferenz\" sein oder die URL eines Geodienstes enthalten." + doc: Abstrakte Oberklasse für die Modellierung von Bereichen. Ein Bereich fasst + die Inhalte eines Plans nach bestimmten Kriterien zusammen. +XP_DatumAttribut: + attributes: + wert: + doc: Attributwert + doc: "Generische Attribute vom Datentyp \"Datum\"\r\n" +XP_DoubleAttribut: + attributes: + wert: + doc: Attributwert + doc: "Generisches Attribut vom Datentyp \"Double\".\r\n" +XP_ExterneReferenz: + attributes: + art: + doc: 'Typisierung der referierten Dokumente: Beliebiges Dokument oder georeferenzierter + Plan.' + beschreibung: + doc: Beschreibung des referierten Dokuments + datum: + doc: Datum des referierten Dokuments + georefMimeType: + doc: "Mime-Type der Georeferenzierungs-Datei. Das Attribut ist nur relevant\ + \ bei Verweisen auf georeferenzierte Rasterbilder.\r\n\r\nDas Attribut ist\ + \ als \"veraltet\" gekennzeichnet und wird in Version 6.0 evtl. wegfallen." + georefURL: + doc: Referenz auf eine Georeferenzierungs-Datei. Das Attribut ist nur relevant + bei Verweisen auf georeferenzierte Rasterbilder. Wenn der XPlanGML Datensatz + und das referierte Dokument in einem hierarchischen Ordnersystem gespeichert + sind, kann die URI auch einen relativen Pfad vom XPlanGML-Datensatz zum Dokument + enthalten. + informationssystemURL: + doc: "URI des Informationssystems., in dem das Dokument gespeichert ist.\r\n\ + \r\nDies Attribut ist als \"veraltet\" gekennzeichnet und wird in Version\ + \ 6.0 evtl. wegfallen." + referenzMimeType: + doc: Mime-Type des referierten Dokumentes + referenzName: + doc: 'Name bzw. Titel des referierten Dokuments ' + referenzURL: + doc: URI des referierten Dokuments, über den auf das Dokument lesend zugegriffen + werden kann. Wenn der XPlanGML Datensatz und das referierte Dokument in einem + hierarchischen Ordnersystem gespeichert sind, kann die URI auch einen relativen + Pfad vom XPlanGML-Datensatz zum Dokument enthalten. + doc: Verweis auf ein extern gespeichertes Dokument oder einen extern gespeicherten, + georeferenzierten Plan. Einer der beiden Attribute " +XP_FPO: + attributes: + position: + doc: Zur Plandarstellung benutzte Flächengeometrie. + doc: "Flächenförmiges Präsentationsobjekt. Entspricht der ALKIS Objektklasse AP_FPO.\r\ + \n" +XP_Gemeinde: + attributes: + ags: + doc: Amtlicher Gemeindeschlüssel (früher Gemeinde-Kennziffer) + gemeindeName: + doc: Name der Gemeinde. + ortsteilName: + doc: Name des Ortsteils + rs: + doc: Regionalschlüssel + doc: Spezifikation einer für die Aufstellung des Plans zuständigen Gemeinde. +XP_GenerAttribut: + attributes: + name: + doc: Name des Generischen Attributs + doc: "Abstrakte Basisklasse für Generische Attribute.\r\n" +XP_Hoehenangabe: + attributes: + abweichenderBezugspunkt: + doc: Textuelle Spezifikation eines Höhenbezugspunktes wenn das Attribut "bezugspunkt" + nicht belegt ist. + abweichenderHoehenbezug: + doc: Textuelle Spezifikation des Höhenbezuges wenn das Attribut "hoehenbezug" + nicht belegt ist. + bezugspunkt: + doc: Bestimmung des Bezugspunktes der Höhenangaben. Wenn weder dies Attribut + noch das Attribut "abweichenderBezugspunkt" belegt sind, soll die Höhenangabe + als vertikale Einschränkung des zugeordneten Planinhalts interpretiert werden. + h: + doc: Maximal zulässige Höhe des Bezugspunktes (bezugspunkt) . + hMax: + doc: 'Maximal zulässige Höhe des Bezugspunktes (bezugspunkt) bei einer Bereichsangabe, + bzw. obere Grenze des vertikalen Gültigkeitsbereiches eines Planinhalts, wenn + "bezugspunkt" nicht belegt ist. In diesem Fall gilt: Ist "hMin" nicht belegt, + gilt die Festlegung bis zur Höhe "hMax".' + hMin: + doc: 'Minimal zulässige Höhe des Bezugspunktes (bezugspunkt) bei einer Bereichsangabe, + bzw. untere Grenze des vertikalen Gültigkeitsbereiches eines Planinhalts, + wenn "bezugspunkt" nicht belegt ist. In diesem Fall gilt: Ist "hMax" nicht + belegt, gilt die Festlegung ab der Höhe "hMin".' + hZwingend: + doc: Zwingend einzuhaltende Höhe des Bezugspunktes (bezugspunkt) , bzw. Beschränkung + der vertikalen Gültigkeitsbereiches eines Planinhalts auf eine bestimmte Höhe. + hoehenbezug: + doc: "Art des Höhenbezuges.\r\n" + doc: Spezifikation einer Angabe zur vertikalen Höhe oder zu einem Bereich vertikaler + Höhen. Es ist möglich, spezifische Höhenangaben (z.B. die First- oder Traufhöhe + eines Gebäudes) vorzugeben oder einzuschränken, oder den Gültigkeitsbereich eines + Planinhalts auf eine bestimmte Höhe ( +XP_IntegerAttribut: + attributes: + wert: + doc: Attributwert + doc: "Generische Attribute vom Datentyp \"Integer\".\r\n" +XP_LPO: + attributes: + position: + doc: Zur Plandarstellung benutzte Liniengeometrie. + doc: "Linienförmiges Präsentationsobjekt Entspricht der ALKIS Objektklasse AP_LPO.\r\ + \n" +XP_LTO: + attributes: + position: + doc: Linienführung des Textes + doc: "Textförmiges Präsentationsobjekt mit linienförmiger Textgeometrie. Entspricht\ + \ der ALKIS-Objektklasse AP_LTO.\r\n" +XP_Nutzungsschablone: + attributes: + spaltenAnz: + doc: Anzahl der Spalten in der Nutzungsschablone + zeilenAnz: + doc: Anzahl der Zeilen in der Nutzungsschablone + doc: "Modelliert eine Nutzungsschablone. Die darzustellenden Attributwerte werden\ + \ zeilenweise in die Nutzungsschablone geschrieben.\r\n" +XP_Objekt: + attributes: + aufschrift: + doc: Spezifischer Text zur Beschriftung von Planinhalten + ebene: + doc: Zuordnung des Objektes zu einer vertikalen Ebene. Der Standard-Ebene 0 + sind Objekte auf der Erdoberfläche zugeordnet. Nur unter diesen Objekten wird + der Flächenschluss hergestellt. Bei Plan-Objekten, die im wesentlichen unterhalb + der Erdoberfläche liesen (z.B. Tunnel), ist ebene < 0. Bei Objekten, die + im wesentlichen oberhalb der Erdoberfläche liegen (z.B. Festsetzungen auf + Brücken), ist ebene > 0. Zwischen Objekten auf Ebene 0 und einer Ebene <> + 0 muss nicht unbedingt eine (vollständige) physikalische Trennung bestehen. + endeBedingung: + doc: Notwendige Bedingung für das Ende der Wirksamkeit eines Planinhalts. + externeReferenz: + doc: Referenz auf ein Dokument oder einen georeferenzierten Rasterplan. + gehoertZuBereich: + doc: Verweis auf den Bereich, zu dem der Planinhalt gehört. Diese Relation sollte + immer belegt werden. In Version 6.0 wird sie in eine Pflicht-Relation umgewandelt + werden. + gesetzlicheGrundlage: + doc: Angabe der gesetzlichen Grundlage des Planinhalts. + gliederung1: + doc: Kennung im Plan für eine erste Gliederungsebene (z.B. GE-E für ein "Eingeschränktes + Gewerbegebiet") + gliederung2: + doc: Kennung im Plan für eine zweite Gliederungsebene (z.B. GE-E 3 für die "Variante + 3 eines eingeschränkten Gewerbegebiets") + hatGenerAttribut: + doc: Erweiterung des definierten Attributsatzes eines Objektes durch generische + Attribute. + hoehenangabe: + doc: Angaben zur vertikalen Lage und Höhe eines Planinhalts. + rechtsstand: + doc: Angabe, ob der Planinhalt bereits besteht, geplant ist, oder zukünftig + wegfallen soll. + refBegruendungInhalt: + doc: Referenz eines raumbezogenen Fachobjektes auf Teile der Begründung. + startBedingung: + doc: Notwendige Bedingung für die Wirksamkeit eines Planinhalts. + text: + doc: Beliebiger Text + uuid: + doc: Eindeutiger Identifier des Objektes. + wirdDargestelltDurch: + doc: Verweis auf ein Präsentationsobjekt, das die Plandarstellung des Fachobjektes + unterstützen soll. + doc: Abstrakte Oberklasse für alle XPlanung-Fachobjekte. Die Attribute dieser Klasse + werden über den Vererbungs-Mechanismus an alle Fachobjekte weitergegeben. +XP_PPO: + attributes: + drehwinkel: + doc: Winkel um den der Text oder die Signatur mit punktförmiger Bezugsgeometrie + aus der Horizontalen gedreht ist, Angabe in Grad. Zählweise im mathematisch + positiven Sinn (von Ost über Nord nach West und Süd). + hat: + doc: Die Relation ermöglicht es, einem punktförmigen Präsentationsobjekt ein + linienförmiges Präsentationsobjekt zuzuweisen. Einziger bekannter Anwendungsfall + ist der Zuordnungspfeil eines Symbols oder einer Nutzungsschablone. + position: + doc: Position des zur Visualisierung benutzten Textes oder Symbols, + skalierung: + doc: Skalierungsfaktor für Symbole. + doc: "Punktförmiges Präsentationsobjekt. Entspricht der ALKIS-Objektklasse AP_PPO.\r\ + \n" +XP_PTO: + attributes: + drehwinkel: + doc: Winkel um den der Text oder die Signatur mit punktförmiger Bezugsgeometrie + aus der Horizontalen gedreht ist, Angabe in Grad. Zählweise im mathematisch + positiven Sinn (von Ost über Nord nach West und Süd). + position: + doc: Position des Textes + doc: "Textförmiges Präsentationsobjekt mit punktförmiger Festlegung der Textposition.\ + \ Entspricht der ALKIS-Objektklasse AP_PTO.\r\n" +XP_Plan: + attributes: + aendert: + doc: Verweis auf einen anderen Plan, der durch den vorliegenden Plan geändert + wird. + begruendungsTexte: + doc: Referenz auf einen Abschnitt der Begründung. Diese Relation darf nicht + verwendet werden, wenn die Begründung als Gesamt-Dokument referiert werden + soll. In diesem Fall sollte über das Attribut externeReferenz eine Objekt + XP_SpezExterneReferent mit typ=1010 (Begruendung) verwendet werden. + beschreibung: + doc: Kommentierende Beschreibung des Plans. + bezugshoehe: + doc: Standard Bezugshöhe (absolut NhN) für relative Höhenangaben von Planinhalten. + erstellungsMassstab: + doc: Der bei der Erstellung des Plans benutzte Kartenmaßstab. + externeReferenz: + doc: 'Referenz auf ein Dokument, einen Datenbankeintrag oder einen georeferenzierten + Rasterplan. ' + genehmigungsDatum: + doc: Datum der Genehmigung des Plans + hatGenerAttribut: + doc: Erweiterung der vorgegebenen Attribute durch generische Attribute. + hoehenbezug: + doc: Bei Höhenangaben im Plan standardmäßig verwendeter Höhenbezug (z.B. Höhe über NN). + internalId: + doc: Interner Identifikator des Plans. + kommentar: + doc: Beliebiger Kommentar zum Plan. + name: + doc: Name des Plans. + nummer: + doc: Nummer des Plans. + raeumlicherGeltungsbereich: + doc: Grenze des räumlichen Geltungsbereiches des Plans. + technHerstellDatum: + doc: Datum, an dem der Plan technisch ausgefertigt wurde. + technischerPlanersteller: + doc: Bezeichnung der Institution oder Firma, die den Plan technisch erstellt + hat. + texte: + doc: Referenz auf einen textlich formulierten Planinhalt. + untergangsDatum: + doc: Datum, an dem der Plan (z.B. durch Ratsbeschluss oder Gerichtsurteil) aufgehoben + oder für nichtig erklärt wurde. + verfahrensMerkmale: + doc: Vermerke der am Planungsverfahren beteiligten Akteure. + wurdeGeaendertVon: + doc: Verweis auf einen anderen Plan, durch den der vorliegende Plan geändert + wurde. + doc: Abstrakte Oberklasse für alle Klassen raumbezogener Pläne. +XP_Plangeber: + attributes: + kennziffer: + doc: Kennziffer des Plangebers. + name: + doc: Name des Plangebers. + doc: Spezifikation der Institution, die für den Plan verantwortlich ist. +XP_Praesentationsobjekt: + attributes: {} + doc: "Entspricht der ALKIS-Objektklasse AP_Darstellung mit dem Unterschied, dass\ + \ auf das Attribut \"positionierungssregel\" verzichtet wurde. Die Klasse darf\ + \ nur als gebundenes Präsentationsobjekt verwendet werden. Die Standard-Darstellung\ + \ des verbundenen Fachobjekts wird dann durch die über stylesheetId spezifizierte\ + \ Darstellung ersetzt. Die Umsetzung dieses Konzeptes ist der Implementierung\ + \ überlassen.\r\n" +XP_Rasterdarstellung: + attributes: + refLegende: + doc: Referenz auf die Legende des Plans. + refScan: + doc: Referenz auf eine georeferenzierte Rasterversion des Basisplans + refText: + doc: Referenz auf die textlich formulierten Inhalte des Plans. + doc: 'Georeferenzierte Rasterdarstellung eines Plans. Das über ' +XP_SPEMassnahmenDaten: + attributes: + klassifizMassnahme: + doc: Klassifikation der Maßnahme + massnahmeKuerzel: + doc: Kürzel der durchzuführenden Maßnahme. + massnahmeText: + doc: Durchzuführende Maßnahme als freier Text. + doc: Spezifikation der Attribute für einer Schutz-, Pflege- oder Entwicklungsmaßnahme. +XP_SpezExterneReferenz: + attributes: + typ: + doc: Typ / Inhalt des referierten Dokuments oder Rasterplans. + doc: Ergänzung des Datentyps XP_ExterneReferenz um ein Attribut zur semantischen + Beschreibung des referierten Dokuments. +XP_StringAttribut: + attributes: + wert: + doc: Attributwert + doc: "Generisches Attribut vom Datentyp \"CharacterString\"\r\n" +XP_TPO: + attributes: + fontSperrung: + doc: Die Zeichensperrung steuert den zusätzlichen Raum, der zwischen 2 aufeinanderfolgende + Zeichenkörper geschoben wird. Er ist ein Faktor, der mit der angegebenen Zeichenhöhe + multipliziert wird, um den einzufügenden Zusatzabstand zu erhalten. Mit der + Abhängigkeit von der Zeichenhöhe wird erreicht, dass das Schriftbild unabhängig + von der Zeichenhöhe gleich wirkt. Der Defaultwert ist 0. + hat: + doc: Die Relation ermöglicht es, einem textlichen Präsentationsobjekt ein linienförmiges + Präsentationsobjekt zuzuweisen. Einziger bekannter Anwendungsfall ist der + Zuordnungspfeil eines Symbols oder einer Nutzungsschablone. + horizontaleAusrichtung: + doc: "Gibt die Ausrichtung des Textes bezüglich der Textgeometrie an.\r\nlinksbündig:\ + \ Der Text beginnt an der Punktgeometrie bzw. am Anfangspunkt der Liniengeometrie.\r\ + \nrechtsbündig: Der Text endet an der Punktgeometrie bzw. am Endpunkt der\ + \ Liniengeometrie\r\nzentrisch: Der Text erstreckt sich von der Punktgeometrie\ + \ gleich weit nach links und rechts bzw. steht auf der Mitte der Standlinie." + schriftinhalt: + doc: Schriftinhalt; enthält den darzustellenden Text. + skalierung: + doc: Skalierungsfaktor der Schriftgröße, bezogen auf die von der interpretierenden + Software festgelegte Standardschrift + vertikaleAusrichtung: + doc: Die vertikale Ausrichtung eines Textes gibt an, ob die Bezugsgeometrie + die Basis (Grundlinie) des Textes, die Mitte oder obere Buchstabenbegrenzung + betrifft. + doc: Abstrakte Oberklasse für textliche Präsentationsobjekte. Entspricht der ALKIS + Objektklasse AP_TPO +XP_TextAbschnitt: + attributes: + gesetzlicheGrundlage: + doc: Gesetzliche Grundlage des Text-Abschnittes + refText: + doc: Referenz auf ein externes Dokument das den zug Textabschnitt enthält. + schluessel: + doc: Schlüssel zur Referenzierung des Abschnitts. + text: + doc: Inhalt eines Abschnitts der textlichen Planinhalte + doc: Ein Abschnitt der textlich formulierten Inhalte des Plans. +XP_URLAttribut: + attributes: + wert: + doc: Attributwert + doc: "Generische Attribute vom Datentyp \"URL\"\r\n" +XP_VerbundenerPlan: + attributes: + nummer: + doc: Nummer (Attribut "nummer" von XP_Plan) des verbundenen Plans + planName: + doc: Name (Attribut "name" von XP_Plan) des verbundenen Plans. + rechtscharakter: + doc: Rechtscharakter der Planänderung. + verbundenerPlan: + doc: Referenz auf einen anderen Plan, der den aktuellen Plan ändert oder von + ihm geändert wird. + doc: "Spezifikation eines anderen Plans, der mit dem Ausgangsplan verbunden ist\ + \ und diesen ändert bzw. von ihm geändert wird.\r\n" +XP_VerfahrensMerkmal: + attributes: + datum: + doc: Datum des Vermerks + signatur: + doc: Unterschrift + signiert: + doc: Angabe, ob die Unterschrift erfolgt ist. + vermerk: + doc: Inhalt des Vermerks. + doc: "Vermerk eines am Planungsverfahrens beteiligten Akteurs.\r\n" +XP_WirksamkeitBedingung: + attributes: + bedingung: + doc: Textlich formulierte Bedingung für die Wirksamkeit oder Unwirksamkeit einer + Festsetzung. + datumAbsolut: + doc: Datum an dem eine Festsetzung wirksam oder unwirksam wird. + datumRelativ: + doc: Zeitspanne, nach der eine Festsetzung wirksam oder unwirksam wird, wenn + die im Attribut bedingung spezifizierte Bedingung erfüllt ist. + doc: Spezifikation von Bedingungen für die Wirksamkeit oder Unwirksamkeit einer + Festsetzung. diff --git a/src/SAGisXPlanung/config/doc_string_config_60.yaml b/src/SAGisXPlanung/config/doc_string_config_60.yaml new file mode 100644 index 0000000..bb42459 --- /dev/null +++ b/src/SAGisXPlanung/config/doc_string_config_60.yaml @@ -0,0 +1,4068 @@ +BP_AbgrabungsFlaeche: + attributes: + abbaugut: + doc: Bezeichnung des Abbauguts. + doc: 'Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen + (§9, Abs. 1, Nr. 17 BauGB)). Hier: Flächen für Abgrabungen und die Gewinnung von + Bodenschätzen.' +BP_AbstandsFlaeche: + attributes: + tiefe: + doc: Absolute Angabe der Tiefe. + doc: "Festsetzung eines vom Bauordnungsrecht abweichenden Maßes der Tiefe der Abstandsfläche\ + \ gemäß § 9 Abs 1. Nr. 2a BauGB\r\n" +BP_AbstandsMass: + attributes: + endWinkel: + doc: Endwinkel für die Planarstellung des Abstandsmaßes (nur relevant für Maßkreise). + Die Winkelwerte beziehen sich auf den Rechtswert (Ost-Richtung) + startWinkel: + doc: Startwinkel für die Plandarstellung des Abstandsmaßes (nur relevant für + Maßkreise). Die Winkelwerte beziehen sich auf den Rechtswert (Ost-Richtung) + typ: + doc: Typ der Massangabe (Maßpfeil oder Maßkreis). + wert: + doc: Wertangabe des Abstandsmaßes. Bei Maßpfeilen (typ == 1000) enthält das + Attribut die Länge des Maßpfeilen (uom = "m"), bei Maßkreisen den von startWinkel + und endWinkel eingeschlossenen Winkel (uom = "grad"). + doc: "Darstellung von Maßpfeilen oder Maßkreisen in BPlänen, um eine eindeutige\ + \ Vermassung einzelner Festsetzungen zu erreichen.\r\n" +BP_AbweichungVonBaugrenze: + attributes: {} + doc: Linienhafte Festlegung des Umfangs der Abweichung von der Baugrenze (§23 Abs. + 3 Satz 3 BauNVO). +BP_AbweichungVonUeberbaubarerGrundstuecksFlaeche: + attributes: {} + doc: Flächenhafte Festlegung des Umfangs der Abweichung von der überbaubaren Grundstücksfläche + (§23 Abs. 3 Satz 3 BauNVO). +BP_AnpflanzungBindungErhaltung: + attributes: + anzahl: + doc: Anzahl der anzupflanzenden Objekte + gegenstand: + doc: Gegenstand der Maßnahme. + istAusgleich: + doc: Gibt an, ob die Fläche oder Maßnahme zum Ausgleich von Eingriffen genutzt + wird. + kronendurchmesser: + doc: Durchmesser der Baumkrone bei zu erhaltenden Bäumen. + massnahme: + doc: "Art der Maßnahme\r\n" + mindesthoehe: + doc: Mindesthöhe des Gegenstands der Festsetzung + pflanzenArt: + doc: Textliche Spezifikation einer Pflanzenart. + pflanztiefe: + doc: Pflanztiefe + doc: "Festsetzung des Anpflanzens von Bäumen, Sträuchern und sonstigen Bepflanzungen;\r\ + \n" +BP_AufschuettungsFlaeche: + attributes: + aufschuettungsmaterial: + doc: Bezeichnung des aufgeschütteten Materials + doc: 'Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen + (§ 9 Abs. 1 Nr. 17 und Abs. 6 BauGB). Hier: Flächen für Aufschüttungen' +BP_AusgleichsFlaeche: + attributes: + massnahme: + doc: "Auf der Fläche durchzuführende Maßnahmen.\r\n" + refLandschaftsplan: + doc: Referenz auf den Landschaftsplan. + refMassnahmenText: + doc: Referenz auf ein Dokument, das die durchzuführenden Maßnahmen beschreibt. + sonstZiel: + doc: Textlich formuliertes Ziel, wenn das Attribut ziel den Wert 9999 (Sonstiges) + hat. + ziel: + doc: Ziel der Ausgleichsmaßnahme + doc: Festsetzung einer Fläche zum Ausgleich im Sinne des § 1a Abs.3 und §9 Abs. + 1a BauGB. +BP_AusgleichsMassnahme: + attributes: + massnahme: + doc: Durchzuführende Ausgleichsmaßnahme. + refLandschaftsplan: + doc: Referenz auf den Landschaftsplan. + refMassnahmenText: + doc: Referenz auf ein Dokument, das die durchzuführenden Maßnahmen beschreibt. + sonstZiel: + doc: Textlich formuliertes Ziel, wenn das Attribut ziel den Wert 9999 (Sonstiges) + hat. + ziel: + doc: Ziel der Ausgleichsmaßnahme + doc: Festsetzung einer Einzelmaßnahme zum Ausgleich im Sinne des § 1a Abs.3 und + §9 Abs. 1a BauGB. +BP_BauGrenze: + attributes: + bautiefe: + doc: Angabe einer Bautiefe. + geschossMax: + doc: Gibt bei geschossweiser Festsetzung die Nummer des Geschosses an, bis zu + der die Festsetzung gilt. Wenn das Attribut nicht belegt ist, gilt die Festsetzung + für alle Geschosse ab einschl. "geschossMin". + geschossMin: + doc: Gibt bei geschossweiser Festsetzung die Nummer des Geschosses an, ab den + die Festsetzung gilt. Wenn das Attribut nicht belegt ist, gilt die Festsetzung + für alle Geschosse bis einschl. "geschossMax". + doc: 'Festsetzung einer Baugrenze (§9 Abs. 1 Nr. 2 BauGB, §22 und 23 BauNVO). Über + die Attribute ' +BP_BauLinie: + attributes: + bautiefe: + doc: Angabe einer Bautiefe. + geschossMax: + doc: Gibt bei geschossweiser Feststzung die Nummer des Geschosses an, bis zu + der die Festsetzung gilt. Wenn das Attribut nicht belegt ist, gilt die Festsetzung + für alle Geschosse ab einschl. "geschossMin". + geschossMin: + doc: Gibt bei geschossweiser Festsetzung die Nummer des Geschosses an, ab den + die Festsetzung gilt. Wenn das Attribut nicht belegt ist, gilt die Festsetzung + für alle Geschosse bis einschl. "geschossMax". + doc: 'Festsetzung einer Baulinie (§9 Abs. 1 Nr. 2 BauGB, §22 und 23 BauNVO). Über + die Attribute ' +BP_BaugebietsTeilFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + FR: + doc: Vorgeschriebene Firstrichtung + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFAntGewerbe: + doc: "Festsetzung nach §6a Abs. (4) Nr. 4 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass \r\nin Gebäuden ein im Bebauungsplan\ + \ bestimmter Anteil der zulässigen \r\nGeschossfläche für gewerbliche Nutzungen\ + \ zu verwenden ist." + GFAntWohnen: + doc: "Festsetzung nach §4a Abs. (4) Nr. 2 bzw. §6a Abs. (4) Nr. 3 BauNVO: Für\ + \ besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann\ + \ festgesetzt werden, dass \r\nin Gebäuden ein im Bebauungsplan bestimmter\ + \ Anteil der zulässigen \r\nGeschossfläche für Wohnungen zu verwenden ist." + GFGewerbe: + doc: "Festsetzung nach §6a Abs. (4) Nr. 4 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass \r\nin Gebäuden eine im Bebauungsplan\ + \ bestimmte Größe der Geschossfläche für gewerbliche Nutzungen zu verwenden\ + \ ist." + GFWohnen: + doc: "Festsetzung nach §4a Abs. (4) Nr. 2 bzw. §6a Abs. (4) Nr. 3 BauNVO: Für\ + \ besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann\ + \ festgesetzt werden, dass \r\nin Gebäuden eine im Bebauungsplan bestimmte\ + \ Größe der Geschossfläche für Wohnungen zu verwenden ist." + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MZspezial: + doc: Textuelle Spezifikation von speziellen Maßzahlen, wie z.B. einer GRZ in + Abhängigkeit von der Bebauungsart. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + VF: + doc: Festsetzung der maximal zulässigen Verkaufsfläche in einem Sondergebiet + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + ZWohn: + doc: 'Festsetzung nach §4a Abs. (4) Nr. 1 bzw. nach §6a Abs. (4) Nr. 2 BauNVO: + Für besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann + festgesetzt werden, dass in Gebäuden oberhalb eines im Bebauungsplan bestimmten + Geschosses nur Wohnungen zulässig sind.' + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximale Anzahl von oberirdisch zurückgesetzten Vollgeschossen als zusätzliche + Staffelgeschosse + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + abweichendeBauweise: + doc: Nähere Bezeichnung einer "Abweichenden Bauweise" ("bauweise" == 3000). + abweichungBauNVO: + doc: Art der zulässigen Abweichung von der BauNVO. + abweichungText: + doc: Textliche Beschreibung der abweichenden Bauweise + allgArtDerBaulNutzung: + doc: Spezifikation der allgemeinen Art der baulichen Nutzung. + bauweise: + doc: Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB). + bauweiseText: + doc: Textuelle Präzisierung oder Ergänzung der durch bauweise spezifizierten + Bauweise. + bebauungRueckwaertigeGrenze: + doc: Festsetzung der Bebauung der rückwärtigen Grundstücksgrenze (§9, Abs. 1, + Nr. 2 BauGB). + bebauungSeitlicheGrenze: + doc: Festsetzung der Bebauung der seitlichen Grundstücksgrenze (§9, Abs. 1, + Nr. 2 BauGB). + bebauungVordereGrenze: + doc: Festsetzung der Bebauung der vorderen Grundstücksgrenze (§9, Abs. 1, Nr. + 2 BauGB). + bebauungsArt: + doc: Detaillierte Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB). + besondereArtDerBaulNutzung: + doc: Festsetzung der Art der baulichen Nutzung (§9, Abs. 1, Nr. 1 BauGB). + dachgestaltung: + doc: Parameter zur Einschränkung der zulässigen Dachformen. + detaillierteArtDerBaulNutzung: + doc: Über eine Codeliste definierte detailliertere Nutzungsart, mit der die + allgemeine und/oder besondere Art der baulichen Nutzung weiter detailliert + werden können. + refGebaeudequerschnitt: + doc: "Referenz auf ein Dokument mit vorgeschriebenen Gebäudequerschnitten.\r\ + \n" + sondernutzung: + doc: Differenziert Sondernutzungen nach §10 und §11 der BauNVO von 1977 und + 1990. Das Attribut wird nur benutzt, wenn besondereArtDerBaulNutzung unbelegt + ist oder einen der Werte 2000 bzw. 2100 hat. + vertikaleDifferenzierung: + doc: Gibt an, ob eine vertikale Differenzierung der Gebäude vorgeschrieben ist. + wohnnutzungEGStrasse: + doc: "Festsetzung nach §6a Abs. (4) Nr. 1 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass in Gebäuden \r\nim Erdgeschoss\ + \ an der Straßenseite eine Wohnnutzung nicht oder nur ausnahmsweise zulässig\ + \ ist." + zugunstenVon: + doc: Angabe des Begünstigten einer Ausweisung. + doc: 'Teil eines Baugebiets mit einheitlicher Art der baulichen Nutzung. Das Maß + der baulichen Nutzung sowie Festsetzungen zur Bauweise oder Grenzbebauung können + innerhalb einer ' +BP_Bereich: + attributes: + gehoertZuPlan: + doc: Referenz eines Bereichs eines Bebauungsplans auf das zugehörige Plan-Objekt. + verfahren: + doc: Verfahrensart der BPlan-Aufstellung oder -Änderung. + doc: Diese Klasse modelliert einen Bereich eines Bebauungsplans, z.B. einen räumlichen + oder sachlichen Teilbereich. +BP_BereichOhneEinAusfahrtLinie: + attributes: + typ: + doc: Typ des Bereiches ohne Ein- und Ausfahrt + doc: "Bereich ohne Ein- und Ausfahrt (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB).\r\n" +BP_BesondererNutzungszweckFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + FR: + doc: Vorgeschriebene Firstrichtung + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MZspezial: + doc: Textuelle Spezifikation von speziellen Maßzahlen, wie z.B. einer GRZ in + Abhängigkeit von der Bebauungsart. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximale Anzahl von oberirdisch zurückgesetzten Vollgeschossen als zusätzliche + Staffelgeschosse + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + abweichendeBauweise: + doc: Nähere Bezeichnung einer "Abweichenden Bauweise" ("bauweise" == 3000). + bauweise: + doc: Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB). + bauweiseText: + doc: Textuelle Präzisierung oder Ergänzung der durch bauweise spezifizierten + Bauweise. + bebauungsArt: + doc: Detaillierte Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB). + dachgestaltung: + doc: Parameter zur Einschränkung der zulässigen Dachformen. + zweckbestimmung: + doc: Angabe des besonderen Nutzungszwecks. + doc: 'Festsetzung einer Fläche mit besonderem Nutzungszweck, der durch besondere + städtebauliche Gründe erfordert wird (§9 Abs. 1 Nr. 9 BauGB.). ' +BP_Dachgestaltung: + attributes: + DN: + doc: Maximal zulässige Dachneigung + DNmax: + doc: Maximale Dachneigung bei einer Bereichsangabe. Das Attribut DNmin muss + ebenfalls belegt sein. + DNmin: + doc: Minimale Dachneigung bei einer Bereichsangabe. Das Attribut DNmax muss + ebenfalls belegt sein. + DNzwingend: + doc: Zwingend vorgeschriebene Dachneigung + dachform: + doc: Erlaubte Dachform + detaillierteDachform: + doc: 'Über eine Codeliste definierte detailliertere Dachform. ' + hoehenangabe: + doc: Beschränkung der Gebäudehöhe für die spezifizierte Dachform. Der hier spezifizierte + Wert hat Priorität gegenüber einem über das Attribut hoehenangabe von XP_Objekt + spezifizierten Wert. + doc: Zusammenfassung von Parametern zur Festlegung der zulässigen Dachformen. +BP_EinfahrtPunkt: + attributes: + typ: + doc: Typ der Einfahrt + doc: Punktförmig abgebildete Einfahrt (§9 Abs. 1 Nr. 11 und Abs. 6 BauGB). +BP_EinfahrtsbereichLinie: + attributes: + typ: + doc: Typ der Einfahrt + doc: "Linienhaft modellierter Einfahrtsbereich (§9 Abs. 1 Nr. 11 und Abs. 6 BauGB).\r\ + \n" +BP_EingriffsBereich: + attributes: {} + doc: "Bestimmt einen Bereich, in dem ein Eingriff nach dem Naturschutzrecht zugelassen\ + \ wird, der durch geeignete Flächen oder Maßnahmen ausgeglichen werden muss.\r\ + \n" +BP_EmissionskontingentLaerm: + attributes: + berechnungsgrundlage: + doc: Technische Grundlage für die Berechnung der Schallleistungspegel + berechnungsgrundlageDatum: + doc: Datum, an dem die zur Berechnung benutzte Vorschrift (Attribut berechnungsgrundlage) + herausgegeben oder aktualisiert wurde. + ekwertNacht: + doc: Emissionskontingent/Schallleistungspegel Nacht in db. + ekwertTag: + doc: Emissionskontingent/Schallleistungspegel Tag in db + erlaeuterung: + doc: Nähere Erläuterung der Berechnungsmethode. + pegelTyp: + doc: Typ der durch ekwertTag und ekwertNacht festgesetzten Schallleistungspegel + doc: Lärmemissionskontingent eines Teilgebietes +BP_EmissionskontingentLaermGebiet: + attributes: + gebietsbezeichnung: + doc: Bezeichnung des Immissionsgebietes + doc: Lärmemissionskontingent eines Teilgebietes, das einem bestimmten Immissionsgebiet + außerhalb des Geltungsbereiches des BPlans zugeordnet ist (Anhang A4 von DIN 45691). +BP_FestsetzungNachLandesrecht: + attributes: + kurzbeschreibung: + doc: Kurzbeschreibung der Festsetzung + doc: Festsetzung nach § 9 Nr. (4) BauGB. +BP_FlaecheOhneFestsetzung: + attributes: {} + doc: Fläche, für die keine geplante Nutzung angegeben werden kann +BP_Flaechenobjekt: + attributes: + flaechenschluss: + doc: 'Zeigt an, ob das Objekt als Flächenschlussobjekt oder Überlagerungsobjekt + gebildet werden soll. Flächenschlussobjekte dürfen sich nicht überlappen, + sondern nur an den Flächenrändern berühren, wobei die jeweiligen Stützpunkte + der Randkurven übereinander liegen müssen. Die Vereinigung der Flächenschlussobjekte + überdeckt den Geltungsbereich des Bebauungsplans vollständig. ' + position: + doc: 'Flächenhafter Raumbezug des Objektes (Eine Einzelfläche oder eine Menge + von Flächen, die sich nicht überlappen dürfen). ' + doc: "Basisklasse für alle Objekte eines Bebauungsplans mit flächenhaftem Raumbezug.\ + \ Die von BP_Flaechenobjekt abgeleiteten Fachobjekte können sowohl als Flächenschlussobjekte\ + \ als auch als Überlagerungsobjekte auftreten.\r\n" +BP_Flaechenschlussobjekt: + attributes: {} + doc: "Basisklasse für alle Objekte eines Bebauungsplans mit flächenhaftem Raumbezug,\ + \ die auf Ebene 0 immer Flächenschlussobjekte sind.\r\n" +BP_FoerderungsFlaeche: + attributes: {} + doc: "Fläche, auf der ganz oder teilweise nur Wohngebäude, die mit Mitteln der sozialen\ + \ Wohnraumförderung gefördert werden könnten, errichtet werden dürfen (§9, Abs.\ + \ 1, Nr. 7 BauGB).\r\n" +BP_FreiFlaeche: + attributes: + nutzung: + doc: Festgesetzte Nutzung der Freifläche. + doc: "Umgrenzung der Flächen, die von der Bebauung freizuhalten sind, und ihre Nutzung\ + \ (§ 9 Abs. 1 Nr. 10 BauGB).\r\n" +BP_GebaeudeFlaeche: + attributes: {} + doc: "Grundrissfläche eines existierenden Gebäudes\r\n" +BP_GebaeudeStellung: + attributes: + typ: + doc: Angabe, auf welche Art und Weise die Stellung des Gebäudes spezifiziert + wird. + doc: Gestaltungs-Festsetzung der Firstrichtung, oder Dach-Ausrichtung, beruhend + auf Landesrecht, gemäß §9 Abs. 4 BauGB. +BP_GemeinbedarfsFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + FR: + doc: Vorgeschriebene Firstrichtung + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MZspezial: + doc: Textuelle Spezifikation von speziellen Maßzahlen, wie z.B. einer GRZ in + Abhängigkeit von der Bebauungsart. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximale Anzahl von oberirdisch zurückgesetzten Vollgeschossen als zusätzliche + Staffelgeschosse + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + abweichendeBauweise: + doc: Nähere Bezeichnung einer "Abweichenden Bauweise" ("bauweise" == 3000). + bauweise: + doc: Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB). + bauweiseText: + doc: Textuelle Präzisierung oder Ergänzung der durch bauweise spezifizierten + Bauweise. + bebauungsArt: + doc: Detaillierte Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB). + dachgestaltung: + doc: Parameter zur Einschränkung der zulässigen Dachformen. + traeger: + doc: 'Trägerschaft einer Gemeinbedarfs-Fläche ' + zugunstenVon: + doc: Angabe des Begünstigten der Ausweisung. + zweckbestimmung: + doc: Zweckbestimmung der festgesetzten Fläche + doc: 'Einrichtungen und Anlagen zur Versorgung mit Gütern und Dienstleistungen des + öffentlichen und privaten Bereichs, hier Flächen für den Gemeindebedarf (§9, Abs. + 1, Nr.5 und Abs. 6 BauGB). ' +BP_GemeinschaftsanlagenFlaeche: + attributes: + Zmax: + doc: Maximale Anzahl von Garagen-Geschossen + eigentuemer: + doc: Relation auf die Baugebietsfläche, zu der die Gemeinschaftsanlagen-Fläche + gehört. + zweckbestimmung: + doc: Zweckbestimmung der Fläche + doc: Fläche für Gemeinschaftsanlagen für bestimmte räumliche Bereiche wie Kinderspielplätze, + Freizeiteinrichtungen, Stellplätze und Garagen (§ 9 Abs. 1 Nr. 22 BauGB) +BP_GemeinschaftsanlagenZuordnung: + attributes: + zuordnung: + doc: Relation auf die zugeordneten Gemeinschaftsanlagen-Flächen, die außerhalb + des Baugebiets liegen. + doc: "Zuordnung von Gemeinschaftsanlagen zu Grundstücken.\r\n" +BP_GenerischesObjekt: + attributes: + zweckbestimmung: + doc: Über eine CodeList definierte Zweckbestimmung des Objektes. + doc: Klasse zur Modellierung aller Inhalte des Bebauungsplans,die durch keine andere + spezifische XPlanung Klasse repräsentiert werden können. +BP_Geometrieobjekt: + attributes: + flaechenschluss: + doc: "Zeigt bei flächenhaftem Raumbezug an, ob das Objekt als Flächenschlussobjekt\ + \ oder Überlagerungsobjekt gebildet werden soll.\r\nFlächenschlussobjekte\ + \ dürfen sich nicht überlappen, sondern nur an den Flächenrändern berühren,\ + \ wobei die jeweiligen Stützpunkte der Randkurven übereinander liegen müssen.\ + \ Die Vereinigung der Flächenschlussobjekte überdeckt den Geltungsbereich\ + \ des Bebauungsplans vollständig. " + flussrichtung: + doc: "Das Attribut ist nur relevant, wenn ein Geometrieobjekt einen linienhaften\ + \ Raumbezug hat. Ist es mit dem Wert true belegt, wird damit ausgedrückt,\ + \ dass der Linie eine Flussrichtung in Digitalisierungsrichtung, bei Attributwert\ + \ \"false\" gegen die Digitalisierungsrichtung zugeordnet ist. In diesem Fall\ + \ darf bei Im- und Export die Digitalisierungsreihenfolge der Stützpunkte\ + \ nicht geändert werden. Wie eine definierte Flussrichtung zu interpretieren\ + \ oder bei einer Plandarstellung zu visualisieren ist, bleibt der Implementierung\ + \ überlassen.\r\nIst der Attributwert false oder das Attribut nicht belegt,\ + \ ist die Digitalisierungsreihenfolge der Stützpunkte irrelevant." + nordwinkel: + doc: Orientierung des Objektes bei punktförmigem Raumbezug als Winkel gegen + die Nordrichtung. Zählweise im geographischen Sinn (von Nord über Ost nach + Süd und West). + position: + doc: Raumbezug - Entweder punktförmig, linienförmig oder flächenhaft, gemischte + Geometrie ist nicht zugelassen. + doc: Basisklasse für alle Objekte eines Bebauungsplans mit variablem Raumbezug. + Das bedeutet, die abgeleiteten Objekte können kontextabhängig mit Punkt-, Linien- + oder Flächengeometrie gebildet. Die Aggregation von Punkten, Linien oder Flächen + ist zulässig, nicht aber die Mischung von Punkt-, Linien- und Flächengeometrie. +BP_GruenFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MZspezial: + doc: Textuelle Spezifikation von speziellen Maßzahlen, wie z.B. einer GRZ in + Abhängigkeit von der Bebauungsart. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximale Anzahl von oberirdisch zurückgesetzten Vollgeschossen als zusätzliche + Staffelgeschosse + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + nutzungsform: + doc: Nutzungsform der festgesetzten Fläche. + zugunstenVon: + doc: Angabe des Begünstigten einer Ausweisung. + zweckbestimmung: + doc: Zweckbestimmung der Grünfläche + doc: 'Festsetzungen von öffentlichen und privaten Grünflächen (§ 9, Abs. 1, Nr. + 15 BauGB). ' +BP_HoehenMass: + attributes: {} + doc: 'Festsetzungen nach §9 Abs. 1 Nr. 1 BauGB für übereinanderliegende Geschosse + und Ebenen und sonstige Teile baulicher Anlagen (§9 Abs.3 BauGB), sowie Hinweise + auf Geländehöhen. Die Höhenwerte werden über das Attribut ' +BP_Immissionsschutz: + attributes: + detaillierteTechnVorkehrung: + doc: Detaillierte Klassifizierung der auf der Fläche zu treffenden baulichen + oder sonstigen technischen Vorkehrungen + laermpegelbereich: + doc: Festlegung der erforderlichen Luftschalldämmung von Außenbauteilen nach + DIN-4109:2016-1. + massgeblAussenLaermpegelNacht: + doc: 'Maßgeblicher Außenlärmpegel (in db) nach DIN 4109-1: 2018-01 für die Nacht.' + massgeblAussenLaermpegelTag: + doc: 'Maßgeblicher Außenlärmpegel (in db) nach DIN 4109-1: 2018-01 für den Tag.' + nutzung: + doc: Festgesetzte Nutzung einer Schutzfläche + sonstLaermpegelbereich: + doc: Spezifikation eines nicht in der DIN-4109:2016-1 spezifizierten Lärmpegelbereiches + über eine Codeliste. Dies Attribut darf nur verwendet werden, wenn laermpegelbereich + den Wert 1700 hat oder unbelegt ist. + technVorkehrung: + doc: Klassifizierung der auf der Fläche zu treffenden baulichen oder sonstigen + technischen Vorkehrungen + typ: + doc: Differenzierung der Immissionsschutz-Fläche + doc: "Festsetzung einer von der Bebauung freizuhaltenden Schutzfläche und ihre Nutzung,\ + \ sowie einer Fläche für besondere Anlagen und Vorkehrungen zum Schutz vor schädlichen\ + \ Umwelteinwirkungen und sonstigen Gefahren im Sinne des Bundes-Immissionsschutzgesetzes\ + \ sowie die zum Schutz vor solchen Einwirkungen oder zur Vermeidung oder Minderung\ + \ solcher Einwirkungen zu treffenden baulichen und sonstigen technischen Vorkehrungen\ + \ (§9, Abs. 1, Nr. 24 BauGB).\r\n" +BP_KennzeichnungsFlaeche: + attributes: + istVerdachtsflaeche: + doc: Legt fest, ob eine Altlast-Verdachtsfläche vorliegt + nummer: + doc: Nummer im Altlastkataster + zweckbestimmung: + doc: Zweckbestimmung der Kennzeichnungs-Fläche. + doc: Flächen für Kennzeichnungen gemäß §9 Abs. 5 BauGB. +BP_KleintierhaltungFlaeche: + attributes: {} + doc: Fläche für die Errichtung von Anlagen für die Kleintierhaltung wie Ausstellungs- + und Zuchtanlagen, Zwinger, Koppeln und dergleichen (§ 9 Abs. 1 Nr. 19 BauGB). +BP_KomplexeSondernutzung: + attributes: + allgemein: + doc: Allgemeine Festlegung der Sondernutzung + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Sondernutzung, die die allgemeine Sondernutzung + näher detailliert. + nutzungText: + doc: Textliche Spezifikation der Sondernutzung. + doc: Spezifikation einer Sondernutzung +BP_KomplexeZweckbestGemeinbedarf: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung der Fläche +BP_KomplexeZweckbestGemeinschaftsanlagen: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: 'Über eine Codelist definierte detailliertere Festlegung der Zweckbestimmung. ' + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung einer Gemeinschaftsanlage +BP_KomplexeZweckbestGruen: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung einer Grünfläche +BP_KomplexeZweckbestLandwirtschaft: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung einer Fläche für die Landwirtschaft. +BP_KomplexeZweckbestNebenanlagen: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: 'Über eine Codeliste definierte detailliertere Festlegung der Zweckbestimmung. ' + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung einer Nebenanlage. +BP_KomplexeZweckbestSpielSportanlage: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung einer Spiel- und Sportanlage. +BP_KomplexeZweckbestVerEntsorgung: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung einer Fläche für Ver- oder Entsorgung +BP_KomplexeZweckbestWald: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung einer Waldfläche. +BP_LandwirtschaftsFlaeche: + attributes: + zweckbestimmung: + doc: Zweckbestimmungen der Ausweisung. + doc: Festsetzungen für die Landwirtschaft (§ 9, Abs. 1, Nr. 18a BauGB) +BP_Linienobjekt: + attributes: + position: + doc: Linienförmiger Raumbezug (Einzelne zusammenhängende Kurve, die aus Linienstücken + und Kreisbögen aufgebaut ist, oder eine Menge derartiger Kurven), + doc: Basisklasse für alle Objekte eines Bebauungsplans mit linienförmigem Raumbezug + (Eine einzelne zusammenhängende Kurve, die aus Linienstücken und Kreisbögen zusammengesetzt + sein kann, oder eine Menge derartiger Kurven). +BP_NebenanlagenAusschlussFlaeche: + attributes: + abweichungText: + doc: Textliche Beschreibung der Einschränkung oder des Ausschlusses. + typ: + doc: Art des Ausschlusses. + doc: Festsetzung einer Fläche für die Einschränkung oder den Ausschluss von Nebenanlagen + nach §14 Absatz 1 Satz 3 BauNVO. +BP_NebenanlagenFlaeche: + attributes: + Zmax: + doc: Maximale Anzahl der Garagengeschosse. + zweckbestimmung: + doc: Zweckbestimmung der Nebenanlagen-Fläche + doc: Fläche für Nebenanlagen, die auf Grund anderer Vorschriften für die Nutzung + von Grundstücken erforderlich sind, wie Spiel-, Freizeit- und Erholungsflächen + sowie die Fläche für Stellplätze und Garagen mit ihren Einfahrten (§9 Abs. 1 Nr. + 4 BauGB) +BP_NichtUeberbaubareGrundstuecksflaeche: + attributes: + nutzung: + doc: Zulässige Nutzung der Fläche + doc: Festlegung der nicht-überbaubaren Grundstücksfläche +BP_NutzungsartenGrenze: + attributes: + detailTyp: + doc: Detaillierter Typ der Abgrenzung, wenn das Attribut typ den Wert 9999 (Sonstige + Abgrenzung) hat. + typ: + doc: Typ der Abgrenzung. Wenn das Attribut nicht belegt ist, ist die Abgrenzung + eine Nutzungsarten-Grenze (Schlüsselnummer 1000). + doc: Abgrenzung unterschiedlicher Nutzung, z.B. von Baugebieten wenn diese nach + PlanzVO in der gleichen Farbe dargestellt werden, oder Abgrenzung unterschiedlicher + Nutzungsmaße innerhalb eines Baugebiets ("Knödellinie", § 1 Abs. 4, § 16 Abs. + 5 BauNVO). +BP_Objekt: + attributes: + laermkontingent: + doc: Festsetzung eines Lärmemissionskontingent nach DIN 45691 + laermkontingentGebiet: + doc: Festsetzung von Lärmemissionskontingenten nach DIN 45691, die einzelnen + Immissionsgebieten zugeordnet sind + richtungssektorGrenze: + doc: Zuordnung einer Richtungssektor-Grenze für die Festlegung zusätzlicher + Lärmkontingente + wirdAusgeglichenDurchABE: + doc: Referenz auf eine Anpflanzungs-, Bindungs- oder Erhaltungsmaßnahme, durch + die ein Eingriff ausgeglichen wird. + wirdAusgeglichenDurchFlaeche: + doc: Referenz auf Ausgleichsfläche, die den Eingriff ausgleicht. + wirdAusgeglichenDurchMassnahme: + doc: Verweis auf eine Ausgleichsmaßnahme, die einen vorgenommenen Eingriff + ausgleicht. + wirdAusgeglichenDurchSPEFlaeche: + doc: Referenz auf eine Schutz-, Pflege- oder Entwicklungs-Fläche, die den Eingriff + ausgleicht. + wirdAusgeglichenDurchSPEMassnahme: + doc: Referenz auf eine Schutz-, Pflege- oder Entwicklungsmaßnahme, durch die + ein Eingriff ausgeglichen wird. + zusatzkontingent: + doc: Festsetzung von Zusatzkontingenten für die Lärmemission, die einzelnen + Richtungssektoren zugeordnet sind. Die einzelnen Richtungssektoren werden + parametrisch definiert. + zusatzkontingentFlaeche: + doc: Festsetzung von Zusatzkontingenten für die Lärmemission, die einzelnen + Richtungssektoren zugeordnet sind. Die einzelnen Richtungssektoren werden + durch explizite Flächen definiert. + doc: Basisklasse für alle raumbezogenen Festsetzungen, Hinweise, Vermerke und Kennzeichnungen + eines Bebauungsplans. +BP_PersGruppenBestimmteFlaeche: + attributes: {} + doc: "Fläche, auf denen ganz oder teilweise nur Wohngebäude errichtet werden dürfen,\ + \ die für Personengruppen mit besonderem Wohnbedarf bestimmt sind (§9, Abs. 1,\ + \ Nr. 8 BauGB)\r\n" +BP_Plan: + attributes: + aenderungenBisDatum: + doc: Datum der berücksichtigten Plan-Änderungen. + aufstellungsbeschlussDatum: + doc: Datum des Aufstellungsbeschlusses. + ausfertigungsDatum: + doc: Datum der Ausfertigung. + auslegungsEndDatum: + doc: End-Datum des Auslegungs-Zeitraums. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + auslegungsStartDatum: + doc: Start-Datum des Auslegungs-Zeitraums. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + bereich: + doc: Referenz eines Bebauungsplans auf einen Bereich + durchfuehrungsVertrag: + doc: Gibt an, ob für das Planungsgebiet einen Durchführungsvertrag (Kombination + aus Städtebaulichen Vertrag und Erschließungsvertrag) gibt. + erschliessungsVertrag: + doc: Gibt an, ob es für den Plan einen Erschließungsvertrag gibt. + gemeinde: + doc: Die für den Plan zuständige Gemeinde. + gruenordnungsplan: + doc: Gibt an, ob für den Plan ein zugehöriger Grünordnungsplan existiert. + inkrafttretensDatum: + doc: Datum des Inkrafttretens. + planArt: + doc: Typ des vorliegenden Bebauungsplans. + planaufstellendeGemeinde: + doc: Die für die ursprüngliche Planaufstellung zuständige Gemeinde, falls diese + nicht unter dem Attribut gemeinde aufgeführt ist. Dies kann z.B. nach Gemeindefusionen + der Fall sein. + plangeber: + doc: Für den Plan verantwortliche Stelle. + rechtsstand: + doc: Aktueller Rechtsstand des Plans. + rechtsverordnungsDatum: + doc: Datum der Rechtsverordnung. + satzungsbeschlussDatum: + doc: Datum des Satzungsbeschlusses. + sonstPlanArt: + doc: Über eine Codeliste spezifizierte "Sonstige Planart", wenn das Attribut + "planArt" den Wert 9999 (Sonstiges) hat. + staedtebaulicherVertrag: + doc: Gibt an, ob es zum Plan einen städtebaulichen Vertrag gibt. + status: + doc: Über eine Codeliste definierter aktueller Status des Plans. + traegerbeteiligungsEndDatum: + doc: End-Datum der Trägerbeteiligung. Bei mehrfacher Trägerbeteiligung können + mehrere Datumsangaben spezifiziert werden. + traegerbeteiligungsStartDatum: + doc: Start-Datum der Trägerbeteiligung. Bei mehrfacher Trägerbeteiligung können + mehrere Datumsangaben spezifiziert werden. + veraenderungssperre: + doc: Spezifiziert die Daten einer Veränderungssperre, die für den gesamten Geltungsbereich + des Plans gilt. + versionBauGB: + doc: Spezifikation der dem Gesamtplan zugrunde liegenden Version des BauGB + versionBauNVO: + doc: Spezifikation der dem Gesamtplan zugrunde liegenden Version der BauNVO + versionSonstRechtsgrundlage: + doc: Spezifikation sonstiger Rechtsgrundlagen des gesamten Plans + doc: Die Klasse modelliert einen Bebauungsplan +BP_Punktobjekt: + attributes: + nordwinkel: + doc: Orientierung des Punktobjektes als Winkel gegen die Nordrichtung. Zählweise + im geographischen Sinn (von Nord über Ost nach Süd und West). + position: + doc: Punktförmiger Raumbezug (Einzelpunkt oder Punktmenge). + doc: Basisklasse für alle Objekte eines Bebauungsplans mit punktförmigem Raumbezug + (Einzelpunkt oder Punktmenge). +BP_RegelungVergnuegungsstaetten: + attributes: + zulaessigkeit: + doc: "Zulässigkeit von Vergnügungsstätten.\r\n" + doc: Festsetzung nach §9 Abs. 2b BauGB (Zulässigkeit von Vergnügungsstätten). +BP_Richtungssektor: + attributes: + winkelAnfang: + doc: Startwinkel des Emissionssektors + winkelEnde: + doc: Endwinkel des Emissionssektors + zkWertNacht: + doc: Zusatzkontingent Nacht + zkWertTag: + doc: Zusatzkontingent Tag + doc: Spezifikation von Zusatzkontingenten Tag/Nacht der Lärmemission für einen Richtungssektor +BP_RichtungssektorGrenze: + attributes: + winkel: + doc: Richtungswinkel der Sektorengrenze + doc: Linienhafte Repräsentation einer Richtungssektor-Grenze +BP_SchutzPflegeEntwicklungsFlaeche: + attributes: + istAusgleich: + doc: Gibt an, ob die Fläche zum Ausgleich von Eingriffen genutzt wird. + massnahme: + doc: Durchzuführende Maßnahme. + refLandschaftsplan: + doc: Referenz auf den Landschaftsplan + refMassnahmenText: + doc: Referenz auf ein Dokument zur Beschreibung der durchzuführenden Maßnahmen. + sonstZiel: + doc: Textlich formuliertes Ziel, wenn das Attribut ziel den Wert 9999 (Sonstiges) + hat. + ziel: + doc: Ziel der auf der Fläche durchzuführenden Maßnahmen. + doc: Umgrenzung von Flächen für Maßnahmen zum Schutz, zur Pflege und zur Entwicklung + von Natur und Landschaft (§9 Abs. 1 Nr. 20 und Abs. 4 BauGB) +BP_SchutzPflegeEntwicklungsMassnahme: + attributes: + istAusgleich: + doc: Gibt an, ob die Maßnahme zum Ausgleich von Eingriffen genutzt wird. + massnahme: + doc: Durchzuführende Maßnahme. + refLandschaftsplan: + doc: Referenz auf den Landschaftsplan. + refMassnahmenText: + doc: Referenz auf ein Dokument, das die durchzuführenden Maßnahmen beschreibt. + sonstZiel: + doc: Textlich formuliertes Ziel, wenn das Aztribut ziel den Wert 9999 (Sonstiges) + hat. + ziel: + doc: Ziel der Maßnahme + doc: Maßnahmen zum Schutz, zur Pflege und zur Entwicklung von Natur und Landschaft + (§9 Abs. 1 Nr. 20 und Abs. 4 BauGB). +BP_SpezielleBauweise: + attributes: + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken. + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + sonstTyp: + doc: Über eine Codeliste definierter Typ der speziellen Bauweise, wenn typ den + Wert 9999 (Sonstiges) hat. + typ: + doc: Typ der speziellen Bauweise. + wegerecht: + doc: Relation auf Angaben zu Wegerechten. + doc: Festsetzung der speziellen Bauweise / baulichen Besonderheit eines Gebäudes + oder Bauwerks. +BP_SpielSportanlagenFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MZspezial: + doc: Textuelle Spezifikation von speziellen Maßzahlen, wie z.B. einer GRZ in + Abhängigkeit von der Bebauungsart. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximale Anzahl von oberirdisch zurückgesetzten Vollgeschossen als zusätzliche + Staffelgeschosse + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + zugunstenVon: + doc: Angabe des Begünstigten einer Ausweisung. + zweckbestimmung: + doc: Zweckbestimmung der festgesetzten Fläche. + doc: 'Einrichtungen und Anlagen zur Versorgung mit Gütern und Dienstleistungen des + öffentlichen und privaten Bereichs, hier Flächen für Sport- und Spielanlagen (§9, + Abs. 1, Nr. 5 und Abs. 6 BauGB). ' +BP_StrassenbegrenzungsLinie: + attributes: + bautiefe: + doc: Minimaler Abstand der Bebauung von der Straßenbegrenzungslinie. + doc: "Straßenbegrenzungslinie (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB) .\r\n" +BP_Strassenkoerper: + attributes: + typ: + doc: Notwendige Maßnahme zur Herstellung des Straßenkörpers. + doc: "Flächen für Aufschüttungen, Abgrabungen und Stützmauern, soweit sie zur Herstellung\ + \ des Straßenkörpers erforderlich sind (§9, Abs. 1, Nr. 26 BauGB).\r\n" +BP_TechnischeMassnahmenFlaeche: + attributes: + technischeMassnahme: + doc: Beschreibung der Maßnahme + zweckbestimmung: + doc: Klassifikation der durchzuführenden Maßnahmen nach §9, Abs. 1, Nr. 23 BauGB. + doc: Fläche für technische oder bauliche Maßnahmen nach § 9, Abs. 1, Nr. 23 BauGB. +BP_TextAbschnittFlaeche: + attributes: {} + doc: Bereich, in dem bestimmte Textliche Festsetzungen gültig sind, die über die + Relation " +BP_UeberbaubareGrundstuecksFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + FR: + doc: Vorgeschriebene Firstrichtung + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFAntGewerbe: + doc: "Festsetzung nach §6a Abs. (4) Nr. 4 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass \r\nin Gebäuden ein im Bebauungsplan\ + \ bestimmter Anteil der zulässigen \r\nGeschossfläche für gewerbliche Nutzungen\ + \ zu verwenden ist." + GFAntWohnen: + doc: "Festsetzung nach §4a Abs. (4) Nr. 2 bzw. §6a Abs. (4) Nr. 3 BauNVO: Für\ + \ besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann\ + \ festgesetzt werden, dass \r\nin Gebäuden ein im Bebauungsplan bestimmter\ + \ Anteil der zulässigen \r\nGeschossfläche für Wohnungen zu verwenden ist." + GFGewerbe: + doc: "Festsetzung nach §6a Abs. (4) Nr. 4 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass \r\nin Gebäuden eine im Bebauungsplan\ + \ bestimmte Größe der Geschossfläche für gewerbliche Nutzungen zu verwenden\ + \ ist." + GFWohnen: + doc: "Festsetzung nach §4a Abs. (4) Nr. 2 bzw. §6a Abs. (4) Nr. 3 BauNVO: Für\ + \ besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann\ + \ festgesetzt werden, dass \r\nin Gebäuden eine im Bebauungsplan bestimmte\ + \ Größe der Geschossfläche für Wohnungen zu verwenden ist." + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MZspezial: + doc: Textuelle Spezifikation von speziellen Maßzahlen, wie z.B. einer GRZ in + Abhängigkeit von der Bebauungsart. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + VF: + doc: Festsetzung der maximal zulässigen Verkaufsfläche in einem Sondergebiet + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + ZWohn: + doc: 'Festsetzung nach §4a Abs. (4) Nr. 1 bzw. nach §6a Abs. (4) Nr. 2 BauNVO: + Für besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann + festgesetzt werden, dass in Gebäuden oberhalb eines im Bebauungsplan bestimmten + Geschosses nur Wohnungen zulässig sind.' + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximale Anzahl von oberirdisch zurückgesetzten Vollgeschossen als zusätzliche + Staffelgeschosse + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + abweichendeBauweise: + doc: "Nähere Bezeichnung einer \"Abweichenden Bauweise\" (\"bauweise == 3000\"\ + ).\r\nDieser Wert hat Priorität gegenüber einer im umschließenden Baugebiet\ + \ (BP_BaugebietsTeilFlaeche) getroffenen Festsetzung" + baugrenze: + doc: Referenz auf eine Baugrenze, die auf der Randkurve der Fläche verläuft. + baulinie: + doc: Referenz auf eine Baulinie, die auf der Randkurve der Fläche verläuft. + bauweise: + doc: "Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB).\r\nDieser Wert hat\ + \ Priorität gegenüber einer im umschließenden Baugebiet (BP_BaugebietsTeilFlaeche)\ + \ getroffenen Festsetzung" + bauweiseText: + doc: Textuelle Präzisierung oder Ergänzung der durch bauweise spezifizierten + Bauweise. + bebauungRueckwaertigeGrenze: + doc: "Festsetzung der Bebauung der rückwärtigen Grundstücksgrenze (§9, Abs.\ + \ 1, Nr. 2 BauGB).\r\nDieser Wert hat Priorität gegenüber einer im umschließenden\ + \ Baugebiet (BP_BaugebietsTeilFlaeche) getroffenen Festsetzung." + bebauungSeitlicheGrenze: + doc: "Festsetzung der Bebauung der seitlichen Grundstücksgrenze (§9, Abs. 1,\ + \ Nr. 2 BauGB).\r\nDieser Wert hat Priorität gegenüber einer im umschließenden\ + \ Baugebiet (BP_BaugebietsTeilFlaeche) getroffenen Festsetzung." + bebauungVordereGrenze: + doc: "Festsetzung der Bebauung der vorderen Grundstücksgrenze (§9, Abs. 1, Nr.\ + \ 2 BauGB).\r\nDieser Wert hat Priorität gegenüber einer im umschließenden\ + \ Baugebiet (BP_BaugebietsTeilFlaeche) getroffenen Festsetzung." + bebauungsArt: + doc: "Detaillierte Festsetzung der Bauweise (§9, Abs. 1, Nr. 2 BauGB).\r\nDieser\ + \ Wert hat Priorität gegenüber einer im umschließenden Baugebiet (BP_BaugebietsTeilFlaeche)\ + \ getroffenen Festsetzung." + dachgestaltung: + doc: Parameter zur Einschränkung der zulässigen Dachformen. + geschossMax: + doc: Gibt bei geschossweiser Festsetzung die Nummer des Geschosses an, bis zu + der die Festsetzung gilt. Wenn das Attribut nicht belegt ist, gilt die Festsetzung + für alle Geschosse ab einschl. "geschossMin". + geschossMin: + doc: Gibt bei geschossweiser Festsetzung die Nummer des Geschosses an, ab den + die Festsetzung gilt. Wenn das Attribut nicht belegt ist, gilt die Festsetzung + für alle Geschosse bis einschl. "geschossMax". + refGebaeudequerschnitt: + doc: "Referenz auf ein Dokument mit vorgeschriebenen Gebäudequerschnitten.\r\ + \nDieser Wert hat Priorität gegenüber einer im umschließenden Baugebiet (BP_BaugebietsTeilFlaeche)\ + \ getroffenen Festsetzung." + vertikaleDifferenzierung: + doc: "Gibt an, ob eine vertikale Differenzierung der Gebäude vorgeschrieben\ + \ ist.\r\nDieser Wert hat Priorität gegenüber einer im umschließenden Baugebiet\ + \ (BP_BaugebietsTeilFlaeche) getroffenen Festsetzung." + wohnnutzungEGStrasse: + doc: "Festsetzung nach §6a Abs. (4) Nr. 1 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass in Gebäuden \r\nim Erdgeschoss\ + \ an der Straßenseite eine Wohnnutzung nicht oder nur ausnahmsweise zulässig\ + \ ist." + doc: 'Festsetzung der überbaubaren Grundstücksfläche (§9, Abs. 1, Nr. 2 BauGB). + Über die Attribute ' +BP_Ueberlagerungsobjekt: + attributes: {} + doc: Basisklasse für alle Objekte eines Bebauungsplans mit flächenhaftem Raumbezug, + die immer Überlagerungsobjekte sind. +BP_UnverbindlicheVormerkung: + attributes: + vormerkung: + doc: Text der Vormerkung. + doc: "Unverbindliche Vormerkung späterer Planungsabsichten.\r\n" +BP_VerEntsorgung: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MZspezial: + doc: Textuelle Spezifikation von speziellen Maßzahlen, wie z.B. einer GRZ in + Abhängigkeit von der Bebauungsart. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximale Anzahl von oberirdisch zurückgesetzten Vollgeschossen als zusätzliche + Staffelgeschosse + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + zugunstenVon: + doc: Angabe des Begünstigten der Ausweisung. + zweckbestimmung: + doc: Zweckbestimmung der Festsetzung.. + doc: 'Flächen und Leitungen für Versorgungsanlagen, für die Abfallentsorgung und + Abwasserbeseitigung sowie für Ablagerungen (§9 Abs. 1, Nr. 12, 14 und Abs. 6 BauGB). ' +BP_Veraenderungssperre: + attributes: + daten: + doc: Daten der Veränderungssperre + doc: 'Ausweisung einer Veränderungssperre, die nicht den gesamten Geltungsbereich + des Plans umfasst. ' +BP_VeraenderungssperreDaten: + attributes: + beschlussDatum: + doc: 'Beschlussdatum der Veränderungssperre ' + endDatum: + doc: Enddatum der Veränderungssperre + refBeschluss: + doc: Referenz auf das Dokument des Veränderungssperre-Beschlusses. + startDatum: + doc: Datum, ab dem die Veränderungssperre gilt + verlaengerung: + doc: Gibt an, ob die Veränderungssperre bereits ein- oder zweimal verlängert + wurde + doc: Spezifikation der Daten für eine Veränderungssperre +BP_WaldFlaeche: + attributes: + betreten: + doc: Festlegung zusätzlicher, normalerweise nicht-gestatteter Aktivitäten, die + in dem Wald ausgeführt werden dürfen, nach §14 Abs. 2 Bundeswaldgesetz. + eigentumsart: + doc: Festlegung der Eigentumsart des Waldes + zweckbestimmung: + doc: Funktion der Waldfläche + doc: Festsetzung von Waldflächen (§ 9, Abs. 1, Nr. 18b BauGB). +BP_Wegerecht: + attributes: + breite: + doc: Breite des Wegerechts bei linienförmiger Ausweisung der Geometrie. + istSchmal: + doc: Gibt an, ob es sich um eine "schmale Fläche" handelt gem. Planzeichen + 15.5 der PlanZV handelt. + thema: + doc: Beschreibung des Rechtes. + typ: + doc: Typ des Wegerechts + zugunstenVon: + doc: Inhaber der Rechte. + doc: Festsetzung von Flächen, die mit Geh-, Fahr-, und Leitungsrechten zugunsten + der Allgemeinheit, eines Erschließungsträgers, oder eines beschränkten Personenkreises + belastet sind (§ 9 Abs. 1 Nr. 21 und Abs. 6 BauGB). +BP_WohngebaeudeFlaeche: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + FR: + doc: Vorgeschriebene Firstrichtung + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFAntGewerbe: + doc: "Festsetzung nach §6a Abs. (4) Nr. 4 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass \r\nin Gebäuden ein im Bebauungsplan\ + \ bestimmter Anteil der zulässigen \r\nGeschossfläche für gewerbliche Nutzungen\ + \ zu verwenden ist." + GFAntWohnen: + doc: "Festsetzung nach §4a Abs. (4) Nr. 2 bzw. §6a Abs. (4) Nr. 3 BauNVO: Für\ + \ besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann\ + \ festgesetzt werden, dass \r\nin Gebäuden ein im Bebauungsplan bestimmter\ + \ Anteil der zulässigen \r\nGeschossfläche für Wohnungen zu verwenden ist." + GFGewerbe: + doc: "Festsetzung nach §6a Abs. (4) Nr. 4 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass \r\nin Gebäuden eine im Bebauungsplan\ + \ bestimmte Größe der Geschossfläche für gewerbliche Nutzungen zu verwenden\ + \ ist." + GFWohnen: + doc: "Festsetzung nach §4a Abs. (4) Nr. 2 bzw. §6a Abs. (4) Nr. 3 BauNVO: Für\ + \ besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann\ + \ festgesetzt werden, dass \r\nin Gebäuden eine im Bebauungsplan bestimmte\ + \ Größe der Geschossfläche für Wohnungen zu verwenden ist." + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MZspezial: + doc: Textuelle Spezifikation von speziellen Maßzahlen, wie z.B. einer GRZ in + Abhängigkeit von der Bebauungsart. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + VF: + doc: Festsetzung der maximal zulässigen Verkaufsfläche in einem Sondergebiet + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + ZWohn: + doc: 'Festsetzung nach §4a Abs. (4) Nr. 1 bzw. nach §6a Abs. (4) Nr. 2 BauNVO: + Für besondere Wohngebiete und urbane Gebiete oder Teile solcher Gebiete kann + festgesetzt werden, dass in Gebäuden oberhalb eines im Bebauungsplan bestimmten + Geschosses nur Wohnungen zulässig sind.' + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximale Anzahl von oberirdisch zurückgesetzten Vollgeschossen als zusätzliche + Staffelgeschosse + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + abweichendeBauweise: + doc: Nähere Bezeichnung einer "Abweichenden Bauweise" ("bauweise" == 3000). + abweichungBauNVO: + doc: Art der zulässigen Abweichung von der BauNVO. + abweichungText: + doc: Texliche Beschreibung der abweichenden Bauweise. + bauweise: + doc: Festsetzung der Bauweise ( §9 Absatz 2d BauGB). + bauweiseText: + doc: Textuelle Präzisierung oder Ergänzung der durch bauweise spezifizierten + Bauweise. + bebauungRueckwaertigeGrenze: + doc: Festsetzung der Bebauung der rückwärtigen Grundstücksgrenze (§9, Abs. 1, + Nr. 2d BauGB). + bebauungSeitlicheGrenze: + doc: Festsetzung der Bebauung der seitlichen Grundstücksgrenze (§9, Abs. 1, + Nr. 2d BauGB). + bebauungVordereGrenze: + doc: Festsetzung der Bebauung der vorderen Grundstücksgrenze (§9, Abs. 1, Nr. + 2d BauGB). + bebauungsArt: + doc: Detaillierte Festsetzung der Bauweise (§9, Abs. 1, Nr. 2d BauGB). + dachgestaltung: + doc: Parameter zur Einschränkung der zulässigen Dachformen. + refGebaeudequerschnitt: + doc: "Referenz auf ein Dokument mit vorgeschriebenen Gebäudequerschnitten.\r\ + \n" + typ: + doc: Festlegung der zu errichtenden Gebäude gemäß §9 Absatz 2d Nr. 1 - 3 BauGB + vertikaleDifferenzierung: + doc: Gibt an, ob eine vertikale Differenzierung der Gebäude vorgeschrieben ist. + wohnnutzungEGStrasse: + doc: "Festsetzung nach §6a Abs. (4) Nr. 1 BauNVO: Für urbane Gebiete oder Teile\ + \ solcher Gebiete kann festgesetzt werden, dass in Gebäuden \r\nim Erdgeschoss\ + \ an der Straßenseite eine Wohnnutzung nicht oder nur ausnahmsweise zulässig\ + \ ist." + zugunstenVon: + doc: Angabe des Begünstigten einer Ausweisung. + doc: 'Fläche für die Errichtung von Wohngebäuden in einem Bebauungsplan zur Wohnraumversorgung + gemäß §9 Absatz 2d BauGB. Das Maß der baulichen Nutzung sowie Festsetzungen zur + Bauweise oder Grenzbebauung können innerhalb einer ' +BP_ZentralerVersorgungsbereich: + attributes: {} + doc: 'Zentraler Versorgungsbereich gem. § 9 Abs. 2a BauGB ' +BP_ZusatzkontingentLaerm: + attributes: + bezeichnung: + doc: Bezeichnung des Kontingentes + richtungssektor: + doc: Spezifikation der Richtungssektoren + doc: Parametrische Spezifikation von zusätzlichen Lärmemissionskontingenten für + einzelne Richtungssektoren (DIN 45691, Anhang 2). +BP_ZusatzkontingentLaermFlaeche: + attributes: + bezeichnung: + doc: Bezeichnung des Kontingentes + richtungssektor: + doc: Spezifikation des zugehörigen Richtungssektors + doc: Flächenhafte Spezifikation von zusätzlichen Lärmemissionskontingenten für einzelne + Richtungssektoren (DIN 45691, Anhang 2). +FP_Abgrabung: + attributes: + abbaugut: + doc: Bezeichnung des Abbauguts. + doc: 'Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen + (§5, Abs. 2, Nr. 8 BauGB). Hier: Flächen für Abgrabungen und die Gewinnung von + Bodenschätzen.' +FP_AnpassungKlimawandel: + attributes: + detailMassnahme: + doc: Detaillierung der durch das Attribut massnahme festgelegten Maßnahme über + eine Codeliste. + massnahme: + doc: Klassifikation der Massnahme + doc: 'Anlagen, Einrichtungen und sonstige Maßnahmen, die der Anpassung an den Klimawandel + dienen ' +FP_Aufschuettung: + attributes: + aufschuettungsmaterial: + doc: Bezeichnung des aufgeschütteten Materials + doc: 'Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen + (§5, Abs. 2, Nr. 8 BauGB). Hier: Flächen für Aufschüttungen.' +FP_AusgleichsFlaeche: + attributes: + massnahme: + doc: Auf der Fläche durchzuführende Maßnahme. + refLandschaftsplan: + doc: Referenz auf den Landschaftsplan. + refMassnahmenText: + doc: Referenz auf ein Dokument in dem die Massnahmen beschrieben werden. + sonstZiel: + doc: Textlich formuliertes Ziel, wenn das Attribut ziel den Wert 9999 (Sonstiges) + hat. + ziel: + doc: Ziel der auf der Fläche durchzuführenden Maßnahmen. + doc: Flächen und Maßnahmen zum Ausgleich gemäß § 5, Abs. 2a BauBG. +FP_BebauungsFlaeche: + attributes: + BMZ: + doc: Angabe einer maximalen Baumassenzahl als Maß der baulichen Nutzung. + GFZ: + doc: Angabe einer maximalen Geschossflächenzahl als Maß der baulichen Nutzung. + GFZdurchschnittlich: + doc: Durchschnittliche GFZ im Baugebiet + GFZmax: + doc: Maximale Geschossflächenzahl bei einer Bereichsangabe (GFZmin muss ebenfalls + spezifiziert werden). + GFZmin: + doc: Minimale Geschossflächenzahl bei einer Bereichsangabe (GFZmax muss ebenfalls + spezifiziert werden). + GRZ: + doc: Angabe einer maximalen Grundflächenzahl als Maß der baulichen Nutzung. + abweichungBauNVO: + doc: Art der zulässigen Abweichung von der BauNVO. + allgArtDerBaulNutzung: + doc: Angabe der allgemeinen Art der baulichen Nutzung. + besondereArtDerBaulNutzung: + doc: Angabe der besonderen Art der baulichen Nutzung. + detaillierteArtDerBaulNutzung: + doc: Über eine Codeliste definierte detailliertere Art der baulichen Nutzung, + mit der die allgemeine und/oder besondere Art der baulichen Nutzung näher + detailliert werden + sondernutzung: + doc: Differenziert Sondernutzungen nach §10 und §11 der BauNVO von 1977 und + 1990. Das Attribut wird nur benutzt, wenn besondereArtDerBaulNutzung unbelegt + ist oder einen der Werte 2000 bzw. 2100 hat + doc: Darstellung einer für die Bebauung vorgesehenen Fläche (§ 5, Abs. 2, Nr. 1 + BauGB). +FP_Bereich: + attributes: + gehoertZuPlan: + doc: Referenz auf den Flächennutzungsplan, zu dem das Bereichsobjekt gehört. + doc: Diese Klasse modelliert einen Bereich eines Flächennutzungsplans. +FP_DarstellungNachLandesrecht: + attributes: + detailZweckbestimmung: + doc: Über eine Codeliste definierte detaillierte Zweckbestimmung der Planinhalts- + kurzbeschreibung: + doc: Textuelle Beschreibung des Planinhalts. + doc: Inhalt des Flächennutzungsplans, der auf einer spezifischen Rechtsverordnung + eines Bundeslandes beruht. +FP_FlaecheOhneDarstellung: + attributes: {} + doc: Fläche, für die keine geplante Nutzung angegben werden kann +FP_Flaechenobjekt: + attributes: + flaechenschluss: + doc: 'Zeigt an, ob das Objekt als Flächenschlussobjekt oder Überlagerungsobjekt + gebildet werden soll. Flächenschlussobjekte dürfen sich nicht überlappen, + sondern nur an den Flächenrändern berühren, wobei die jeweiligen Stützpunkte + der Randkurven übereinander liegen müssen. Die Vereinigung der Flächenschlussobjekte + überdeckt den Geltungsbereich des Flächennutzungsplans vollständig. ' + position: + doc: 'Flächenhafter Raumbezug des Objektes (Eine Einzelfläche oder eine Menge + von Flächen, die sich nicht überlappen dürfen). ' + doc: 'Basisklasse für alle Objekte eines Flächennutzungsplans mit flächenhaftem + Raumbezug (eine Einzelfläche oder eine Menge von Flächen, die sich nicht überlappen + dürfen). Die von ' +FP_Flaechenschlussobjekt: + attributes: {} + doc: "Basisklasse für alle Objekte eines Flächennutzungsplans mit flächenhaftem\ + \ Raumbezug, die auf Ebene 0 immer Flächenschlussobjekte sind.\r\n" +FP_Gemeinbedarf: + attributes: + traeger: + doc: Trägerschaft einer Gemeinbedarfs-Fläche + zugunstenVon: + doc: Angabe des Begünstigten der Ausweisung. + zweckbestimmung: + doc: Zweckbestimmung der Fläche + doc: Darstellung von Flächen für den Gemeinbedarf nach § 5, Abs. 2, Nr. 2 BauGB. +FP_GenerischesObjekt: + attributes: + zweckbestimmung: + doc: Über eine Codeliste definierte Zweckbestimmung des Generischen Objekts. + doc: Klasse zur Modellierung aller Inhalte des FPlans, die durch keine spezifische + XPlanung-Klasse repräsentiert werden können. +FP_Geometrieobjekt: + attributes: + flaechenschluss: + doc: "Zeigt bei flächenhaftem Raumbezug an, ob das Objekt als Flächenschlussobjekt\ + \ oder Überlagerungsobjekt gebildet werden soll.\r\nFlächenschlussobjekte\ + \ dürfen sich nicht überlappen, sondern nur an den Flächenrändern berühren,\ + \ wobei die jeweiligen Stützpunkte der Randkurven übereinander liegen müssen.\ + \ Die Vereinigung der Flächenschlussobjekte überdeckt den Geltungsbereich\ + \ des Flächennutzungsplans vollständig. " + flussrichtung: + doc: "Das Attribut ist nur relevant, wenn ein Geometrieobjekt einen linienhaften\ + \ Raumbezug hat. Ist es mit dem Wert true belegt, wird damit ausgedrückt,\ + \ dass der Linie eine Flussrichtung in Digitalisierungsrichtung zugeordnet\ + \ ist. In diesem Fall darf bei Im- und Export die Digitalisierungsreihenfolge\ + \ der Stützpunkte nicht geändert werden. Wie eine definierte Flussrichtung\ + \ zu interpretieren oder bei einer Plandarstellung zu visualisieren ist,\ + \ bleibt der Implementierung überlassen.\r\nIst der Attributwert false oder\ + \ das Attribut nicht belegt, ist die Digitalisierungsreihenfolge der Stützpunkte\ + \ irrelevant." + nordwinkel: + doc: Orientierung des Objektes bei punkförmigem Raumbezug als Winkel gegen die + Nordrichtung. Zählweise im geographischen Sinn (von Nord über Ost nach Süd + und West). + position: + doc: Raumbezug - Entweder punktförmig, linienförmig oder flächenhaft, gemischte + Geometrie ist nicht zugelassen. + doc: Basisklasse für alle Objekte eines Flächennutzungsplans mit variablem Raumbezug. + Ein konkretes Objekt muss entweder punktförmigen, linienförmigen oder flächenhaften + Raumbezug haben, gemischte Geometrie ist nicht zugelassen. +FP_Gruen: + attributes: + nutzungsform: + doc: Nutzungsform der Grünfläche. + zugunstenVon: + doc: Begünstigter der Ausweisung + zweckbestimmung: + doc: Zweckbestimmung der Grünfläche. + doc: Darstellung einer Grünfläche nach § 5, Abs. 2, Nr. 5 BauGB, +FP_KeineZentrAbwasserBeseitigungFlaeche: + attributes: {} + doc: Baufläche, für die eine zentrale Abwasserbeseitigung nicht vorgesehen ist (§ + 5, Abs. 2, Nr. 1 BauGB). +FP_Kennzeichnung: + attributes: + istVerdachtsflaeche: + doc: Legt fest, ob eine Altlast-Verdachtsfläche vorliegt + nummer: + doc: Nummer in einem Altlastkataster + zweckbestimmung: + doc: Zweckbestimmung der Kennzeichnung. + doc: Kennzeichnung gemäß §5 Abs. 3 BauGB. +FP_KomplexeSondernutzung: + attributes: + allgemein: + doc: Allgemeine Festlegung der Sondernutzung + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Sondernutzung, die die allgemeine Sondernutzung + näher detaillieren. + nutzungText: + doc: Textliche Spezifikation der Sondernutzung. + doc: Spezifikation einer Sondernutzung +FP_KomplexeZweckbestGemeinbedarf: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung der Fläche +FP_KomplexeZweckbestGruen: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung einer Grünfläche +FP_KomplexeZweckbestLandwirtschaft: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung einer Fläche für die Landwirtschaft. +FP_KomplexeZweckbestSpielSportanlage: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung einer Spiel- und Sportanlage. +FP_KomplexeZweckbestVerEntsorgung: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung einer Fläche für Ver- oder Entsorgung +FP_KomplexeZweckbestWald: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung einer Waldfläche. +FP_Landwirtschaft: + attributes: + zweckbestimmung: + doc: Zweckbestimmungen der Ausweisung + doc: Darstellung einer Landwirtschaftsfläche nach §5, Abs. 2, Nr. 9a. +FP_Linienobjekt: + attributes: + position: + doc: Linienförmiger Raumbezug (Einzelne zusammenhängende Kurve, die aus Linienstücken + und Kreisbögen aufgebaut ist, oder eine Menge derartiger Kurven). + doc: "Basisklasse für alle Objekte eines Flächennutzungsplans mit linienförmigem\ + \ Raumbezug (eine einzelne zusammenhängende Kurve, die aus Linienstücken und Kreisbögen\ + \ zusammengesetzt sein kann, oder eine Menge derartiger Kurven).\r\n" +FP_Nutzungsbeschraenkung: + attributes: + detaillierteTechnVorkehrung: + doc: Detaillierte Klassifizierung der auf der Fläche zu treffenden baulichen + oder sonstigen technischen Vorkehrungen. + nutzung: + doc: Festgesetzte Nutzung der Fläche + technVorkehrung: + doc: Klassifizierung der auf der Fläche zu treffenden baulichen oder sonstigen + technischen Vorkehrungen + typ: + doc: Differenzierung der Fläche gemäß BauGB + doc: Umgrenzungen von Flächen für Nutzungsbeschränkungen oder für Vorkehrungen zum + Schutz gegen schädliche Umwelteinwirkungen im Sinne des Bundes-Immissionsschutzgesetzes + (§ 5, Abs. 2, Nr. 6 BauGB) +FP_Objekt: + attributes: + spezifischePraegung: + doc: Über eine Codeliste definierte spezifische bauliche Prägung einer Darstellung. + vonGenehmigungAusgenommen: + doc: Angabe, ob Teile des Flächennutzungsplans nach §6 Abs. 3 BauGB von der + Genehmigung ausgenommen sind + wirdAusgeglichenDurchFlaeche: + doc: Referenz auf eine Ausgleichsfläche, die den Eingriff ausgleicht. + wirdAusgeglichenDurchSPE: + doc: Referenz auf eine Schutz-,Pflege- oder Entwicklungsmaßnahme, die den Eingriff + ausgleicht. + doc: Basisklasse für alle Fachobjekte des Flächennutzungsplans. +FP_Plan: + attributes: + aenderungenBisDatum: + doc: Datum, bis zu dem Änderungen des Plans berücksichtigt wurden. + aufstellungsbeschlussDatum: + doc: Datum des Plan-Aufstellungsbeschlusses. + auslegungsEndDatum: + doc: End-Datum der öffentlichen Auslegung. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + auslegungsStartDatum: + doc: Start-Datum der öffentlichen Auslegung. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + bereich: + doc: Referenz auf einen Bereich des Flächennutzungsplans + entwurfsbeschlussDatum: + doc: Datum des Entwurfsbeschlusses + gemeinde: + doc: Zuständige Gemeinde + planArt: + doc: Typ des FPlans + planaufstellendeGemeinde: + doc: Die für die ursprüngliche Planaufstellung zuständige Gemeinde, falls diese + nicht unter dem Attribut gemeinde aufgeführt ist. Dies kann z.B. nach Gemeindefusionen + der Fall sein. + planbeschlussDatum: + doc: Datum des Planbeschlusses + plangeber: + doc: "Für die Planung zuständige Institution\r\n" + rechtsstand: + doc: Aktueller Rechtsstand des Plans. + sachgebiet: + doc: Sachgebiet eines Teilflächennutzungsplans. + sonstPlanArt: + doc: Sonstiger Typ des FPlans bei "planArt" == 9999. + status: + doc: Über eine Codeliste definierter Status des Plans. + traegerbeteiligungsEndDatum: + doc: End-Datum der Trägerbeteiligung. Bei mehrfacher Trägerbeteiligung können + mehrere Datumsangaben spezifiziert werden. + traegerbeteiligungsStartDatum: + doc: Start-Datum der Trägerbeteiligung. Bei mehrfacher Trägerbeteiligung können + mehrere Datumsangaben spezifiziert werden. + verfahren: + doc: Verfahren nach dem ein FPlan aufgestellt oder geändert wird. + versionBauGB: + doc: Spezifikation der dem Gesamtplan zugrunde liegenden Version des BauGB + versionBauNVO: + doc: Spezifikation der dem Gesamtplan zugrunde liegenden Version der BauNVO + versionSonstRechtsgrundlage: + doc: Spezifikation sonstiger Rechtsgrundlagen des Gesamtplans + wirksamkeitsDatum: + doc: Datum der Wirksamkeit + doc: Klasse zur Modellierung eines gesamten Flächennutzungsplans. +FP_PrivilegiertesVorhaben: + attributes: + vorhaben: + doc: Nähere Beschreibung des Vorhabens. + zweckbestimmung: + doc: Zweckbestimmungen des Vorhabens. + doc: Standorte für privilegierte Außenbereichsvorhaben und für sonstige Anlagen + in Außenbereichen gem. § 35 Abs. 1 und 2 BauGB. +FP_Punktobjekt: + attributes: + nordwinkel: + doc: Orientierung des Punktobjektes als Winkel gegen die Nordrichtung. Zählweise + im geographischen Sinn (von Nord über Ost nach Süd und West). + position: + doc: Punktförmiger Raumbezug (Einzelpunkt oder Punktmenge). + doc: Basisklasse für alle Objekte eines Flächennutzungsplans mit punktförmigem Raumbezug + (Einzelpunkt oder Punktmenge). +FP_SchutzPflegeEntwicklung: + attributes: + istAusgleich: + doc: Gibt an, ob die Maßnahme zum Ausgleich eines Eingriffs benutzt wird. + massnahme: + doc: Durchzuführende Maßnahme. + sonstZiel: + doc: Textlich formuliertes Ziel, wenn das Attribut ziel den Wert 9999 (Sonstiges) + hat. + ziel: + doc: Ziel der Maßnahmen + doc: Umgrenzung von Flächen für Maßnahmen zum Schutz, zur Pflege und zur Entwicklung + von Natur und Landschaft (§5 Abs. 2, Nr. 10 BauGB) +FP_SpielSportanlage: + attributes: + zugunstenVon: + doc: Bebünstigter der Ausweisung + zweckbestimmung: + doc: Allgemeine Zweckbestimmung der Fläche. + doc: Darstellung von Flächen für Spiel- und Sportanlagen nach §5, Abs. 2, Nr. 2 + BauGB. +FP_TextAbschnittFlaeche: + attributes: {} + doc: Bereich, in dem bestimmte Textliche Darstellungen gültig sind, die über die + Relation " +FP_Ueberlagerungsobjekt: + attributes: {} + doc: Basisklasse für alle Objekte eines Flächennutzungsplans mit flächenhaftem Raumbezug, + die immer Überlagerungsobjekte sind. +FP_UnverbindlicheVormerkung: + attributes: + vormerkung: + doc: Text der Vormerkung. + doc: "Unverbindliche Vormerkung späterer Planungsabsichten\r\n" +FP_VerEntsorgung: + attributes: + zugunstenVon: + doc: Angabe des Begünstigten der Ausweisung. + zweckbestimmung: + doc: Zweckbestimmung der Fläche. + doc: Flächen für Versorgungsanlagen, für die Abfallentsorgung und Abwasserbeseitigung + sowie für Ablagerungen (§5, Abs. 2, Nr. 4 BauGB). +FP_VorbehalteFlaeche: + attributes: + vorbehalt: + doc: Textliche Formulierung des Vorbehalts. + doc: Flächen auf denen bestimmte Vorbehalte wirksam sind. +FP_WaldFlaeche: + attributes: + betreten: + doc: Festlegung zusätzlicher, normalerweise nicht-gestatteter Aktivitäten, die + in dem Wald ausgeführt werden dürfen, nach §14 Abs. 2 Bundeswaldgesetz. + eigentumsart: + doc: Festlegung der Eigentumsart des Waldes + zweckbestimmung: + doc: Funktion der Waldfläche. + doc: Darstellung von Waldflächen nach §5, Abs. 2, Nr. 9b, +FP_ZentralerVersorgungsbereich: + attributes: + auspraegung: + doc: Über eine Codeliste definierte Ausprägung eines zentralen Versorgungsbereiches. + doc: "Darstellung nach § 5 Abs. 2 Nr. 2d (Ausstattung des Gemeindegebietes mit zentralen\ + \ Versorgungsbereichen).\r\n" +LP_AdressatKomplex: + attributes: + adressatArt: + doc: Art des Adressaten, an den sich das Ziel, das Erfordernis oder die Maßnahme + richtet. + adressatText: + doc: Erläuterung zu sonstigem Adressaten(adressatArt = 9999) oder ergänzende + Information zu ausgewähltem anderen Adressaten. + doc: "Angaben zu Adressaten, an den sich das Ziel, das Erfordernis oder die Maßnahme\ + \ richtet.\r\n" +LP_Bereich: + attributes: + gehoertZuPlan: + doc: Referenz auf den Landschaftsplan, zu dem der Bereich gehört. + doc: Die Klasse modelliert einen Planbereich mit landschaftsplanerischen Darstellungen + bzw. Festsetzungen sowie gutachterliche Aussagen der Landschaftsplanung. +LP_BioVfBiotoptypKomplex: + attributes: + bioVfBiotoptyp_BKompV: + doc: "Biotoptypen-Katalog der Bundeskompensationsverordnung (Anlage 2 (zu §\ + \ 5 Absatz 1 BKompV )\r\n" + bioVfBiotoptyp_LandesKS: + doc: "Biotoptyp gem. eines Landeskartierschlüssels\r\n" + bioVfBiotoptyp_Text: + doc: Textliche Angabe zum Biotoptypen; alternativ oder zusätzlich zur Auswahl + eines über Codelisten bereitgestellten Kartierschlüssels von Bund, Land oder + FFH-LRT. + bioVf_FFH_LRT: + doc: FFH-Lebensraumtypen gem. Anhang I der Fauna Flora Habitatrichtlinie + doc: 'Komplexes Attribut mit ' +LP_BioVfPflanzenArtKomplex: + attributes: + bioVfPflanzenArtName: + doc: Textliche Angabe zur Pflanzen-Art, botanisch oder Trivialnamen + bioVfPflanzenRechtlicherSchutz: + doc: "Rechtliche Grundlage für den Schutz einer Pflanzenart an.\r\n" + bioVfPflanzenRechtlicherSchutzText: + doc: "Textliche Erläuterung zu sonstigem Rechtlichen Schutz (bioVfPflanzenRechtlicherSchutz\ + \ = 9999) oder ergänzende Information zu ausgewähltem rechtlichem Schutz.\r\ + \n" + bioVfPflanzenSystematik: + doc: 'Gibt systematische Einordnung einer Pflanzenart an:' + bioVfPflanzenSystematikText: + doc: Textliche Erläuterung zu sonstiger PflanzenartenSystematik (wenn bioVfPflanzenSystematik= + 9999) oder ergänzende Information zu einer anderen Systematischen Einordnung. + doc: Komplexes Attribut mit Angaben zum Planungsgegenstand Biologische Vielfalt + / Pflanzen +LP_BioVfTiereArtKomplex: + attributes: + bioVfTierArtHabitatanforderung: + doc: Gibt besondere Habitatanforderungen einer Tierart an. + bioVfTierArtHabitatanforderungText: + doc: Erläuterung zu Sonstige Habitatanforderungen oder ergänzende Information + zu ausgewählten Habitatanforderungen. + bioVfTierArtName: + doc: 'Textliche Angabe zur Tierart; alternativ oder zusätzlich auch bioVfTierArtSystematik ' + bioVfTierArtRechtlicherSchutz: + doc: "Rechtliche Grundlage für den Schutz einer Tierart an.\r\n" + bioVfTierArtRechtlicherSchutzText: + doc: "Textliche Erläuterung zu sonstigem rechtlichen Schutz (bioVfTierArtRechtlicherSchutz\ + \ = 9999) oder ergänzende Information zu ausgewähltem Rechtlichem Schutz.\r\ + \n" + bioVfTiereSystematik: + doc: "Gibt systematische Einordnung einer Tierart an\r\n" + bioVfTiereSystematikText: + doc: Textliche Erläuterung zu sonstiger Tierartensystematik (bioVfTiereSystematik= + 9999) oder ergänzende Information zu ausgewählter Gruppe. + doc: Komplexes Attribut mit Angaben zum Planungsgegenstand Biologische Vielfalt + / Tiere +LP_BiologischeVielfaltKomplex: + attributes: + bioVfArtFFHAnhangII: + doc: Gibt an, ob Nachweise für Tier- und Pflanzenarten von gemeinschaftlichem + Interesse kartiert wurden, für deren Erhaltung besondere Schutzgebiete ausgewiesen + werden müssen (gemäß Anhang II der FFH-RL). + bioVfBiotoptyp: + doc: Angaben zum Biotoptyp + bioVfPflanzenArt: + doc: Angaben zu Planzenarten als Bestandteil der biologischen Vielfalt. + bioVfTierArt: + doc: "Angaben zu Tierarten als Bestandteil der biologischen Vielfalt.\r\n" + bioVielfaltTypus: + doc: "Zeigt an, auf welchen Bestandteil der Biologischen Vielfalt sich das Objekt\ + \ bezieht\r\n" + doc: Angaben zum Planungsgegenstand „Biologische Vielfalt“ +LP_BiologischeVielfaltTypKomplex: + attributes: + bioVielfaltTyp: + doc: 'Zeigt an, auf welchen Bestandteil der Biologischen Vielfalt sich das Objekt + bezieht:' + bioVielfaltTypText: + doc: Textliche Spezfizierung des Typs/Bestandteils der biologischen Vielfalt, + wenn bioVielfaltTyp = 9999 („Sonstiger Bestandteil Biologische Vielfalt“), + oder ergänzende Information zu anderen Typen der Biologischen Vielfalt + doc: 'Angaben, auf welchen Bestandteil / Typus der Biologischen Vielfalt sich das + Objekt bezieht:' +LP_BiotopverbundBiotopvernetzung: + attributes: + bioVStandortFeuchte: + doc: Zeigt die Ausprägung der Standortverhältnisse des Verbundsystems im Hinblick + auf Feuchtigkeit an. + bioVerbundsystemArt: + doc: Zeigt die Art des Verbundsystems an. + bioVerbundsystemText: + doc: Textliche ergänzende Informationen zu Art und Ausprägung der Standortverhältnisse + des Verbundsystems. + foerdermoeglichkeit: + doc: Nennung von Fördermöglichkeiten für den Biotopverbund oder die Biotopvernetzung. + planungsEbene: + doc: Nutzungsregelung (Klassifikation). + rechtlicheSicherung: + doc: Rechtliche Sicherung für Flächen des Biotopverbunds und der Biotopvernetzung + nach § 21 Abs. 4 BNatSchG. + rechtlicheSicherungText: + doc: Erläuterung zu Sonstiger rechtlicher Sicherung in rechtlicheSicherung oder + ergänzende Information zur ausgewählten rechtlichen Sicherungsart. + typBioVerbund: + doc: Angaben zum Typen des Biotopverbundelementes + doc: Flächen und Elemente für Biotopverbund und Biotopvernetzung. +LP_BodenKomplex: + attributes: + bodenAuspraegung: + doc: Ausprägungen in Bezug auf Boden, an die sich das Ziel, das Erfordernis + oder die Maßnahme richtet. + bodenText: + doc: "Erläuterung zu Sonstiger Boden (bodenAuspraegung = 9999) oder ergänzende\ + \ Information zu anderer ausgewählter Ausprägung des Planungsgegenstandes\ + \ Boden.\r\n" + doc: "Komplexes Attribut mit Angaben in Bezug auf Planungsgegenstand Boden, an die\ + \ sich das Ziel, das Erfordernis oder die Maßnahme richtet..\r\n" +LP_Eingriffsregelung: + attributes: + eingriffsregelungFlaechenTyp: + doc: Angaben zum Flächentyp in Bezug auf Eingriffsregelung + kompensationText: + doc: 'Ergänzende Informationen zur Kompensation. ' + massnahmentyp: + doc: Differenziert in nationalrechtliche und unionsrechtliche Kompensationsmaßnahmen. + umsetzungsstand: + doc: "Zeigt an, ob eine bereit umgesetzte oder angerechnete Kompensationsfläche\ + \ oder -Maßnahme nachrichtlich übernommen oder für neue Kompensationsmaßenahmen\ + \ vorgeschlagen wird. \r\n" + doc: "Planungsaussagen mit Bezug zur Eingriffsregelung und der Bewältigung von Eingriffsfolgen.\r\ + \n" +LP_EingriffsregelungKomplex: + attributes: + eRFlaechenArt: + doc: Differenzierung der Planungsaussagen mit Bezug zur Eingriffsregelung und + der Bewältigung von Eingriffsfolgen. + eRFlaechenArtText: + doc: Textliche Differenzierung der Planungsaussagen mit Bezug zur Eingriffsregelung + und der Bewältigung von Eingriffsfolgen, wenn ERFlaechenArt = 9999. + doc: Datentyp mit Angaben für eine komplexe Eingriffsregelung +LP_ErholungKomplex: + attributes: + erholungFunktionArt: + doc: "Art der Erholungsfunktion oder -Infrastruktur, an die sich\r\n das Ziel,\ + \ das Erfordernis oder die Maßnahme richtet." + erholungFunktionText: + doc: "Erläuterung zu Sonstiger Planungsgegenstand Erholung oder ergänzende Information\ + \ zu einer anderen ausgewählten Erholungsfunktion/Infrastruktur.\r\n\r\n" + doc: "Komplexes Attribut mit Angaben in Bezug auf Planungsgegenstand Erholung, an\ + \ die sich das Ziel, das Erfordernis oder die Maßnahme richtet..\r\n" +LP_GenerischesObjekt: + attributes: + zweckbestimmung: + doc: Über eine Codeliste definierte Zweckbestimmung des Generischen Objektes. + doc: Klasse zur Modellierung aller Inhalte des Landschaftsplans, die durch keine + spezifische XPlanung-Klasse repräsentiert werden können. +LP_Geometrieobjekt: + attributes: + flussrichtung: + doc: "Das Attribut ist nur relevant, wenn ein Geometrieobjekt einen linienhaften\ + \ Raumbezug hat. Ist es mit dem Wert true belegt, wird damit ausgedrückt,\ + \ dass der Linie eine Flussrichtung in Digitalisierungsrichtung zugeordnet\ + \ ist. In diesem Fall darf bei Im- und Export die Digitalisierungsreihenfolge\ + \ der Stützpunkte nicht geändert werden. Wie eine definierte Flussrichtung\ + \ zu interpretieren oder bei einer Plandarstellung zu visualisieren ist,\ + \ bleibt der Implementierung überlassen.\r\nIst der Attributwert false oder\ + \ das Attribut nicht belegt, ist die Digitalisierungsreihenfolge der Stützpunkte\ + \ irrelevant." + nordwinkel: + doc: Orientierung des Objektes bei punkförmigem Raumbezug als Winkel gegen die + Nordrichtung. Zählweise im geographischen Sinn (von Nord über Ost nach Süd + und West). + position: + doc: Raumbezug - Entweder punktförmig, linienförmig oder flächenhaft, gemischte + Geometrie ist nicht zugelassen. + doc: Basisklasse für alle Objekte eines Landschaftsplans mit variablem Raumbezug. + Ein konkretes Objekt muss entweder punktförmigen, linienförmigen oder flächenhaften + Raumbezug haben, gemischte Geometrie ist nicht zugelassen. +LP_KlimaKomplex: + attributes: + klimaArt: + doc: Art des Planungsgegenstand für Klima, an die sich das Ziel, das Erfordernis + oder die Maßnahme richtet. + klimaText: + doc: "Erläuterung zu Sonstiger Planungsgegenstand Schutzgut Klima oder ergänzende\ + \ Information zu ausgewählten Planungsgegenständen.\r\n" + doc: "Komplexes Attribut mit Angaben in Bezug auf Planungsgegenstand Klima, an die\ + \ sich das Ziel, das Erfordernis oder die Maßnahme richtet..\r\n" +LP_LandschaftsbildKomplex: + attributes: + landschaftsbildArt: + doc: Art des Planungsgegenstand für das Landschaftsbild, an die sich das Ziel, + das Erfordernis oder die Maßnahme richtet. + landschaftsbildText: + doc: "Erläuterung zu Sonstiger Planungsgegenstand Schutzgut Landschaftsbild\ + \ oder ergänzende Information zu anderem ausgewähltem Planungsgegenstand Landschaftsbild.\r\ + \n" + doc: "Komplexes Attribut mit Angaben in Bezug auf Planungsgegenstand Landschaftsbild,\ + \ an die sich das Ziel, das Erfordernis oder die Maßnahme richtet..\r\n" +LP_LuftKomplex: + attributes: + luftArt: + doc: Art des Planungsgegenstand für Luft, an die sich das Ziel, das Erfordernis + oder die Maßnahme richtet. + luftText: + doc: "Erläuterung zu Sonstiger Planungsgegenstand Schutzgut Luft oder ergänzende\ + \ Information zu anderem ausgewähltem Planungsgegenstand Luft.\r\n" + doc: "Komplexes Attribut mit Angaben in Bezug auf Planungsgegenstand Luft, an die\ + \ sich das Ziel, das Erfordernis oder die Maßnahme richtet..\r\n" +LP_NutzungseinschraenkungKomplex: + attributes: + hatNutzungseinschraenkung: + doc: Zeigt an, ob eine Einschränkung bestimmter Nutzungen empfohlen/vorgeschlagen + wird. + nutzungseinschraenkungText: + doc: "Textfeld, Erläuterung von Art und Umfang der empfohlenen/vorgeschlagenen\ + \ Einschränkung bestimmter Nutzungen.\r\n\r\n" + doc: "Komplexes Attribut mit Angaben zu Nutzungseinschränkungen.\r\n" +LP_Objekt: + attributes: + raumkonkretisierung: + doc: Beschreibung der räumlichen Schärfe des geometrischen Objekts, d.h. wie + konkret die Geometrie in Attribut position die tatsächliche räumliche Gestalt + des Planinhalts darstellt. + rechtsCharText: + doc: Textliche Konkretisierung der rechtlichen Charakterisierung. + referenziertLPObjekt: + doc: "Angaben zu einem anderen LP-Planinhalt, der mit diesem LP-Objekt in Verbindung\ + \ steht.\r\n" + vorschlagIntegrationBLP: + doc: Verweis auf ein Objekt im Modellbereich Bauleitplanung, das von der Landschaftsplanung + als Kopiervorlage zur Übernahme in den FNP/Bebauungsplan vorgeschlagen wird. + Referenziert ein Objekt mit Angabe der Begründung. + vorschlagIntegrationRO: + doc: Verweis auf ein Objekt im Modellbereich Raumordnung, das von der Landschaftsplanung + als Kopiervorlage zur Übernahme in den Raumordnungsplan vorgeschlagen wird. + Referenziert ein Objekt mit Angabe der Begründung. + wirdReferenziertVon: + doc: Rückwärtsreferenz zu einem LP- Objekt, das dieses LP-Objekt referenziert. + doc: Basisklasse für alle spezifischen Inhalte eines Landschaftsplans. +LP_Plan: + attributes: + aenderungenBisDatum: + doc: Datum, bis zu dem Planänderungen berücksichtigt wurden. + aufstellungsbeschlussDatum: + doc: Datum des Aufstellungsbeschlusses + auslegungsEndDatum: + doc: End-Datum der öffentlichen Auslegung. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + auslegungsStartDatum: + doc: Start-Datum der öffentlichen Auslegung. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + bereich: + doc: Referenz auf die Bereiche des Landschaftsplans (einzelne Kartenblätter, + Detailkarten, Übersichtskarten etc.). + bundesland: + doc: Zuständiges Bundesland + endeBedingungen: + doc: Beschreibt Bedingung(en), bis wann ein Plan wirksam ist. + entwurfsbeschlussDatum: + doc: Datum des Entwurfsbeschlusses. + gemeinde: + doc: Die für diesen Plan zuständige Gemeinde. + inkrafttretenDatum: + doc: Datum des Inkrafttretens. + oeffentlichkeitsBetEndDatum: + doc: End-Datum der Öffentlichkeitsbeteiligung. Bei mehrfacher öffentlicher Beteiligung + können mehrere Datumsangaben spezifiziert werden. + oeffentlichkeitsBetStartDatum: + doc: Start-Datum der Öffentlichkeitsbeteiligung. Bei mehrfacher öffentlicher + Beteiligung können mehrere Datumsangaben spezifiziert werden. + planArt: + doc: Typ des vorliegenden Landschaftsplans. + planaufstellendeGemeinde: + doc: Die für die ursprüngliche Planaufstellung zuständige Gemeinde, falls diese + nicht unter dem Attribut gemeinde aufgeführt ist. Dies kann z.B. nach Gemeindefusionen + der Fall sein. + planbeschlussDatum: + doc: Datum des Planbeschlusses. + plangeber: + doc: "Für die Planung zuständige Institution falls von der Gemeinde abweichend,\ + \ z.B. ein Planungsverband.\r\n" + rechtlicheAussenwirkung: + doc: Gibt an, ob der Plan eine rechtliche Außenwirkung hat. + rechtsstand: + doc: Rechtsstand des Plans + sonstPlanArt: + doc: Spezifikation einer "Sonstigen Planart", wenn kein Plantyp aus der Enumeration + LP_PlanArt zutreffend ist. Das Attribut "planArt" muss in diesem Fall der + Wert 9999 haben. + sonstVerfahrensDatum: + doc: Sonstiges Verfahrens-Datum (Verfahrens-Art siehe sonstVerfahrensText) + sonstVerfahrensText: + doc: Textliche Präzisierung des sonstigen Verfahrens gem. Attribut sonstVerfahrensDatum + startBedingungen: + doc: Beschreibt Bedingung(en), ab wann ein Plan wirksam ist. + tOeBbeteiligungsEndDatum: + doc: End-Datum der ÖB-Trägerbeteiligung. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + tOeBbeteiligungsStartDatum: + doc: Start-Datum der ÖB-Trägerbeteiligung. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + veroeffentlichungsDatum: + doc: Datum der Veröffentlichung des Plans. + doc: Die Klasse modelliert ein Planwerk mit landschaftsplanerischen gutachterlichen + Aussagen, Darstellungen bzw. Festsetzungen. +LP_ReferenzLPObjekt: + attributes: + beschreibung: + doc: Textliche Beschreibung der Beziehung zwischen diesem und dem referenzierten + Landschaftsplanungs-Objekt. + verbundenesLPObjekt: + doc: Referenz auf einen anderen Planinhalt, der mit diesem LP-Objekt in Verbindung + steht. + doc: "Verweis auf einen anderen Planinhalt (LP-Objekt) dieses Plans, der mit diesem\ + \ LP-Objekt in Verbindung steht.\r\n" +LP_SPEKomplex: + attributes: + schutzPflegeEntwicklungText: + doc: 'Textfeld, Erläuterung sonstigem Ziel Schutz, Pflege und Entwicklung oder + ergänzende Information zu ausgewählten Differenzierungen. ' + schutzPflegeEntwicklungTyp: + doc: Differenzierung der Ziele, der Erfordernisse oder der Maßnahmen von Naturschutz + und Landschaftspflege. + doc: "Angaben zur Differenzierung der Schutz-, Pflege und Entwicklungsziele von\ + \ Naturschutz und Landschaftspflege.\r\n" +LP_SchutzBestimmterTeileVonNaturUndLandschaft: + attributes: + artDerFestlegung: + doc: Kategorien der Schutzgebiete und sonstigen geschützten Bestandteilen von + Natur und Landschaft nach Kap. 4 Bundesnaturschutzgesetz (BNatSchG) und Landes-Naturschutzgesetzen. + artDerFestlegungText: + doc: Erläuterung zu Sonstige Schutzgebietskategorie oder ergänzende Information + zu ausgewählten Schutzgebietskategorien. + detailGesetzlGeschBiotopLR: + doc: Über eine Codeliste definierte weitere gesetzlich geschützte Biotope z.B. + nach Landesrecht. + gesetzlGeschBiotop: + doc: Angabe einer gesetzlich geschützten Biotoptypen-Art gemäß §30 Abs. 2 Ziffern + 1-6 BNatSchG. + gesetzlGeschBiotopText: + doc: Erläuterung zu Sonstige gesetzlich geschützte Biotope (wenn gesetzlichGeschuetztBiotop + = 9999) oder ergänzende Information zu anderem ausgewählten gesetzlich geschütztem + Biotoptyp. + name: + doc: Name des Schutzgebietes, z.B. "Leineaue zwischen Ruthe und Koldingen". + nummer: + doc: Schutzgebietsnummer, eindeutiges Kennzeichen des Gebiets, z.B. "NSG HA + 203". + rechtsstandSchG: + doc: Rechtsstand des Schutzgebiets oder des geschützten Bestandteils von Natur + und Landschaft. + rechtsstandSchGText: + doc: Erläuterung zu Sonstigem Rechtsstand (wenn rechtsstandSchG = 9999) oder + ergänzende Information zu ausgewähltem Rechtsstand. + schutzzone: + doc: "Angabe der Schutzzone für zonierte Schutzgebiete, insbesondere Biosphärenreservate\ + \ und Nationalparke.\r\n" + schutzzonenText: + doc: Erläuterung zu Sonstige Schutzzone (Konformitätsregel, wenn schutzzone + = 9999, dann Belegung Pflicht oder ergänzende Information zu ausgewählten + Schutzzonen. + doc: Schutzgebietskategorien gemäß Kapitel 4 BNatSchG „Schutz bestimmter Teile von + Natur und Landschaft“. +LP_SchutzgutKomplex: + attributes: + schutzgutArt: + doc: Schutzgüter von Naturschutz und Landschaftspflege, abgeleitet aus § 1 BNatSchG. + schutzgutText: + doc: Erläuterung zu sonstigem Schutzgut (schutzgutArt = 9999) oder ergänzende + Information zu ausgewähltem anderen Schutzgut. + doc: 'Komplexes Attribut mit Angaben zum Schutzgut/Schutzgütern, auf die sich die + Ziele, Erfordernisse und Maßnahmen richten. ' +LP_TextAbschnittObjekt: + attributes: {} + doc: Bereich, in dem bestimmte textliche Festsetzungen gültig sind, die über die + Relation " +LP_TypBioVerbundKomplex: + attributes: + flaechenTypBV: + doc: Flächentyp des Biotopverbunds nach § 21 Abs. 3 BNatSchG. + flaechentypBVSpeziell: + doc: Differenzierung des speziellen Flächentyp des Biotopverbunds nach § 21 + Abs. 3 BNatSchG. + flaechentypSpeziellText: + doc: Textliche Ergänzungen zu flaechentypBVSpeziell. + doc: Datentyp mit Angaben zu Typen des Biotopverbunds. +LP_VorschlagIntegrationBauleitplanung: + attributes: + begruendung: + doc: Begründung, warum die Landschaftsplanung die Übernahme in die Bauleitplanung + vorschlägt / empfiehlt / festsetzt. + refObjektBauleitplanung: + doc: Referenz auf ein Objekt im Modellbereich Bauleitplanung, das von der Landschaftsplanung + als Kopiervorlage zur Übernahme in einen Bauleitplan vorgeschlagen wird. + doc: Vorschlag zur Integration in die Bauleitplanung +LP_VorschlagIntegrationRaumordnung: + attributes: + begruendung: + doc: Begründung, warum die Landschaftsplanung die Übernahme in die Bauleitplanung + vorschlägt / empfiehlt / festsetzt. + refObjektRaumordnung: + doc: Referenz auf ein Objekt im Modellbereich Raumordnung, das von der Landschaftsplanung + als Kopiervorlage zur Übernahme in einen Raumordnungsplan vorgeschlagen wird. + doc: Vorschlag zur Integration in die Raumordnung +LP_WasserKomplex: + attributes: + wasserAuspraegung: + doc: Ausprägungen in Bezug auf Planungsgegenstand Wasser, an die sich das Ziel, + das Erfordernis oder die Maßnahme richtet. + wasserText: + doc: "Erläuterung zu Sonstiges (wasserAuspraegung = 9999) oder ergänzende Information\ + \ zu anderer ausgewählter Ausprägung des Planungsgegenstandes Wasser.\r\n" + doc: "Komplexes Attribut mit Angaben in Bezug auf Planungsgegenstand Wasser, an\ + \ die sich das Ziel, das Erfordernis oder die Maßnahme richtet..\r\n" +LP_ZielDimNatSchLaPflKomplex: + attributes: + zielDimensionText: + doc: Erläuterung zu sonstigem Teilziel (teilZielArt = 9999) oder ergänzende + Information zu ausgewähltem Teilziel. + zielDimensionTyp: + doc: Teilziele des Naturschutzes und der Landschaftspflege gemäß § 1 Abs. 1 + Ziffern 1. bis 3. BNatSchG. + doc: Zieldimension von Naturschutz und Landschaftspflege; Teilziele des Naturschutzes + und der Landschaftspflege gemäß § 1 Abs. 1 Ziffern 1. bis 3. BNatSchG +LP_ZieleErfordernisseMassnahmen: + attributes: + adressat: + doc: Adressat(en), an den oder die sich die Aussage der Landschaftsplanung richtet. + biologischeVielfalt: + doc: "Angaben zum Planungsgegenstand „Biologische Vielfalt“\r\n" + boden: + doc: Angaben zum Planungsgegenstand Boden + erholung: + doc: Angaben zum Planungsgegenstand Erholung in Natur und Landschaft. + foerdermoeglichkeit: + doc: "Textliche Nennung von Fördermöglichkeiten für Maßnahmen.\r\n" + freiraeume: + doc: "Textliche Erläuterung zum Planungsgegenstand Freiräume.\r\n" + klima: + doc: Angaben zum Planungsgegenstand Klima + landschaftsbild: + doc: Angaben zum Planungsgegenstand Landschaftsbild + luft: + doc: Angaben zum Planungsgegenstand Luft + nutzungseinschraenkung: + doc: Angaben zu Nutzungseinschränkungen. + schutzPflegeEntwicklung: + doc: "Differenzierung Schutz-, Pflege und Entwicklungsziele von Naturschutz\ + \ und Landschaftspflege.\r\n" + schutzgut: + doc: Angaben zum Schutzgut / zu den Schutzgütern. + wasser: + doc: Angaben zum Planungsgegenstand Wasser + zielDimNatSchLaPfl: + doc: Zieldimension von Naturschutz und Landschaftspflege; Teilziele des Naturschutzes + und der Landschaftspflege gemäß § 1 Abs. 1 Ziffern 1. bis 3. BNatSchG + zieleErfordernisseMassnahmen: + doc: Zeigt an, ob es sich bei dem Objekt um ein Ziel, ein Erfordernis und/oder + um eine Maßnahme gemäß Kapitel 2 BNatSchG handelt. + doc: Ziele, Erfordernisse und Maßnahmen für Naturschutz und Landschaftspflege gem. + Kapitel 2 BNatSchG. +RP_Achse: + attributes: + typ: + doc: Klassifikation verschiedener Achsen. + doc: Achsen bündeln i.d.R. Verkehrs- und Versorgungsinfrastruktur und enthalten + eine relativ dichte Folge von Siedlungskonzentrationen und Zentralen Orten. +RP_Bereich: + attributes: + gehoertZuPlan: + doc: Relation auf den zugehörigen Plan + geltungsmassstab: + doc: (Rechtlicher) Geltungsmaßstab des Bereichs. + versionBROG: + doc: Bekanntmachungs-Datum der zugrunde liegenden Version des ROG. + versionBROGText: + doc: Titel der zugrunde liegenden Version des Bundesraumordnungsgesetzes. + versionLPLG: + doc: Bekanntmachungs-Datum des zugrunde liegenden Landesplanungsgesetzes. + versionLPLGText: + doc: Titel des zugrunde liegenden Landesplanungsgesetzes. + doc: Die Klasse RP_Bereich modelliert einen Bereich eines Raumordnungsplans. Bereiche + strukturieren Pläne räumlich und inhaltlich. +RP_Bodenschutz: + attributes: + typ: + doc: Klassifikation von Bodenschutztypen. + doc: Maßnahmen, die zum Schutz von Böden und Bodenfunktionen (auch vorsorglich) + unter dem Aspekt des Natur- und Umweltschutzes getroffen werden. +RP_Einzelhandel: + attributes: + typ: + doc: Klassifikation von Einzelhandelstypen. + doc: Einzelhandelsstruktur und -funktionen. +RP_Energieversorgung: + attributes: + primaerenergieTyp: + doc: Klassifikation von der mit der Infrastruktur in Beziehung stehenden Primärenergie. + spannung: + doc: Klassifikation von Spannungen. + typ: + doc: Klassifikation von Energieversorgungs-Einrichtungen. + doc: Infrastruktur zur Energieversorgung. Beinhaltet Energieerzeugung und die Belieferung + von Verbrauchern mit Nutzenergie. Erneuerbare Energie wie Windkraft wird im Normalfall + auf die Klasse RP_ErneuerbareEnergie im Unterpaket Freiraumstruktur zugeordnet. + Je nach Kontext kann aber auch eine Zuordnung auf RP_Energieversorgung stattfinden. +RP_Entsorgung: + attributes: + abfallTyp: + doc: Klassifikation von mit der Entsorgungsinfrastruktur in Beziehung stehenden + Abfalltypen + istAufschuettungAblagerung: + doc: Gibt an, ob die Entsorgung in Form einer Aufschüttung oder Ablagerung erfolgt + typAE: + doc: Klassifikation von Abfallentsorgung-Infrastruktur. + typAW: + doc: Klassifikation von Abwasser-Infrastruktur. + doc: "Entsorgungs-Infrastruktur Beinhaltet Abfallentsorgung und Abwasserentsorgung.\r\ + \n" +RP_Erholung: + attributes: + besondererTyp: + doc: Klassifikation von besonderen Typen für Tourismus und/oder Erholung. + typErholung: + doc: Klassifikation von Erholungstypen. + typTourismus: + doc: Klassifikation von Tourismustypen. + doc: Freizeit, Erholung und Tourismus. +RP_ErneuerbareEnergie: + attributes: + typ: + doc: Klassifikation von Typen Erneuerbarer Energie. + doc: "Erneuerbare Energie inklusive Windenergienutzung.\r\n" +RP_Forstwirtschaft: + attributes: + typ: + doc: Klassifikation von Forstwirtschaftstypen und Wäldern. + doc: "Forstwirtschaft ist die zielgerichtete Bewirtschaftung von Wäldern.\r\n" +RP_Freiraum: + attributes: + imVerbund: + doc: Zeigt an, ob das Objekt in einem (Freiraum-)Verbund liegt. + istAusgleichsgebiet: + doc: Zeigt an, ob das Objekt ein Ausgleichsgebiet ist. + doc: "Allgemeines Freiraumobjekt.\r\n" +RP_Funktionszuweisung: + attributes: + bezeichnung: + doc: Bezeichnung und/oder Erörterung einer Gebietsfunktion. + typ: + doc: Klassifikation des Gebietes nach Bundesraumordnungsgesetz. + doc: Gebiets- und Gemeindefunktionen. +RP_GenerischesObjekt: + attributes: + typ: + doc: Über eine CodeList definierte Zweckbestimmung der Festlegung. + doc: Klasse zur Modellierung aller Inhalte des Raumordnungsplans, die durch keine + andere Klasse des RPlan-Fachschemas dargestellt werden können. +RP_Geometrieobjekt: + attributes: + flaechenschluss: + doc: Zeigt an, ob für das Objekt Flächenschluss vorliegt. + flussrichtung: + doc: "Das Attribut ist nur relevant, wenn ein Geometrieobjekt einen linienhaften\ + \ Raumbezug hat. Ist es mit dem Wert true belegt, wird damit ausgedrückt,\ + \ dass der Linie eine Flussrichtung in Digitalisierungsrichtung zugeordnet\ + \ ist. In diesem Fall darf bei Im- und Export die Digitalisierungsreihenfolge\ + \ der Stützpunkte nicht geändert werden. Wie eine definierte Flussrichtung\ + \ zu interpretieren oder bei einer Plandarstellung zu visualisieren ist,\ + \ bleibt der Implementierung überlassen.\r\nIst der Attributwert false oder\ + \ das Attribut nicht belegt, ist die Digitalisierungsreihenfolge der Stützpunkte\ + \ irrelevant." + nordwinkel: + doc: Orientierung des Objektes bei punktförmigem Raumbezug als Winkel gegen + die Nordrichtung. Zählweise im geographischen Sinn (von Nord über Ost nach + Süd und West). + position: + doc: Variabler Raumbezug. + doc: Basisklasse für alle Objekte eines Raumordnungsplans mit variablem Raumbezug. + Ein konkretes Objekt muss entweder punktförmigen, linienförmigen oder flächenhaften + Raumbezug haben, gemischte Geometrie ist nicht zugelassen. RP_Geometrieobjekt + selbst ist abstrakt. +RP_Gewaesser: + attributes: + gewaesserTyp: + doc: Spezifiziert den Typ des Gewässers. + doc: Gewässer, die nicht andersweitig erfasst werden, zum Beispiel Flüsse oder Seen. +RP_Grenze: + attributes: + sonstTyp: + doc: Erweiterter Typ. + spezifischerTyp: + doc: Spezifischer Typ der Grenze + typ: + doc: Typ der Grenze + doc: Grenzen. +RP_GruenzugGruenzaesur: + attributes: + typ: + doc: Klassifikation von Zäsurtypen. + doc: Grünzüge und kleinräumigere Grünzäsuren sind Ordnungsinstrumente zur Freiraumsicherung. + Teilweise werden Grünzüge auch Trenngrün genannt. +RP_Hochwasserschutz: + attributes: + typ: + doc: Klassifikation von Hochwasserschutztypen. + doc: "Die Klasse RP_Hochwasserschutz enthält Hochwasserschutz und vorbeugenden Hochwasserschutz.\r\ + \n" +RP_IndustrieGewerbe: + attributes: + typ: + doc: Klassifikation von Industrie- und Gewerbetypen. + doc: Industrie- und Gewerbestrukturen und -funktionen. +RP_Klimaschutz: + attributes: + typ: + doc: Klassifikation von Lufttypen. + doc: (Siedlungs-) Klimaschutz. Beinhaltet zum Beispiel auch Kalt- und Frischluftschneisen. +RP_Kommunikation: + attributes: + typ: + doc: Klassifikation von Kommunikations-Infrastruktur. + doc: Infrastruktur zur Telekommunikation, digitale Infrastruktur oder Kommunikationsinfrastruktur. +RP_Kulturlandschaft: + attributes: + typ: + doc: Klassifikation von Kulturlandschaftstypen. + doc: "Landschaftsbereich mit überwiegend anthropogenen Ökosystemen (historisch geprägt\ + \ und gewachsen). Sie sind nach §2, Nr. 5 des ROG mit ihren Kultur- und Naturdenkmälern\ + \ zu erhalten und zu entwickeln. \r\n" +RP_LaermschutzBauschutz: + attributes: + typ: + doc: Klassifikation von Lärmschutztypen. + doc: Infrastruktur zum Lärmschutz und/oder Bauschutz. +RP_Landwirtschaft: + attributes: + typ: + doc: Klassifikation von Landwirtschaftstypen. + doc: Landwirtschaft, hauptsächlich im ländlichen Raum angesiedelt, erfüllt für die + Gesellschaft wichtige Funktionen in der Produktion- und Versorgung mit Lebensmitteln, + für Freizeit und Freiraum oder zur Biodiversität. +RP_Luftverkehr: + attributes: + typ: + doc: Klassifikation von Luftverkehr-Infrastruktur. + doc: Luftverkehr-Infrastruktur ist Infrastruktur, die im Zusammenhang mit der Beförderung + von Personen, Gepäck, Fracht und Post mit staatlich zugelassenen Luftfahrzeugen, + besonders Flugzeugen steht. +RP_NaturLandschaft: + attributes: + typ: + doc: Klassifikation von Naturschutz, Landschaftsschutz und Naturlandschafttypen. + doc: Naturlandschaften sind von umitellbaren menschlichen Aktivitäten weitestgehend + unbeeinflusst gebliebene Landschaft. +RP_NaturschutzrechtlichesSchutzgebiet: + attributes: + istKernzone: + doc: Gibt an, ob es sich um eine Kernzone handelt. + typ: + doc: Klassifikation des Naturschutzgebietes. + doc: Schutzgebiet nach Bundes-Naturschutzgesetz. +RP_Objekt: + attributes: + bedeutsamkeit: + doc: Bedeutsamkeit eines Objekts. + gebietsTyp: + doc: Gebietstyp eines Objekts. + istZweckbindung: + doc: Zeigt an, ob es sich bei diesem Objekt um eine Zweckbindung handelt. + konkretisierung: + doc: Konkretisierung des Rechtscharakters. + kuestenmeer: + doc: Zeigt an, ob das Objekt im Küstenmeer liegt. + doc: RP_Objekt ist die Basisklasse für alle spezifischen Festlegungen eines Raumordnungsplans. + Sie selbst ist abstrakt, d.h. sie wird selbst nicht als eigenes Objekt verwendet, + sondern vererbt nur ihre Attribute an spezielle Klassen. +RP_Plan: + attributes: + aenderungenBisDatum: + doc: Datum, bis zu dem Planänderungen berücksichtigt wurden. + amtlicherSchluessel: + doc: Amtlicher Schlüssel eines Plans auf Basis des AGS-Schlüssels (Amtlicher + Gemeindeschlüssel). + aufstellungsbeschlussDatum: + doc: Datum des Aufstellungsbeschlusses. + auslegungEndDatum: + doc: End-Datum der öffentlichen Auslegung. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + auslegungStartDatum: + doc: Start-Datum der öffentlichen Auslegung. Bei mehrfacher öffentlicher Auslegung + können mehrere Datumsangaben spezifiziert werden. + bereich: + doc: Relation auf einen Bereich + bundesland: + doc: Zuständige Bundesländer für den Plan. + datumDesInkrafttretens: + doc: Datum des Inkrafttretens des Plans. + entwurfsbeschlussDatum: + doc: Datum des Entwurfsbeschlusses + genehmigungsbehoerde: + doc: Zuständige Genehmigungsbehörde + planArt: + doc: Art des Raumordnungsplans. + planbeschlussDatum: + doc: Datum des Planbeschlusses. + planungsregion: + doc: Kennziffer der Planungsregion. + rechtsstand: + doc: Rechtsstand des Plans. + sonstPlanArt: + doc: Spezifikation einer weiteren Planart (CodeList) bei planArt == 9999. + status: + doc: Status des Plans, definiert über eine CodeList. + teilabschnitt: + doc: Kennziffer des Teilabschnittes. + traegerbeteiligungsEndDatum: + doc: End-Datum der Trägerbeteiligung. Bei mehrfacher Trägerbeteiligung können + mehrere Datumsangaben spezifiziert werden. + traegerbeteiligungsStartDatum: + doc: Start-Datum der Trägerbeteiligung. Bei mehrfacher Trägerbeteiligung können + mehrere Datumsangaben spezifiziert werden. + verfahren: + doc: Verfahrensstatus des Plans. + doc: Die Klasse RP_Plan modelliert einen Raumordnungsplan. +RP_Planungsraum: + attributes: + planungsraumBeschreibung: + doc: Textliche Beschreibung eines Planungsrauminhalts. + doc: Modelliert einen allgemeinen Planungsraum. +RP_RadwegWanderweg: + attributes: + typ: + doc: Klassifikation von Radwegen und Wanderwegen. + doc: Radwege und Wanderwege. Straßenbegleitend oder selbstständig geführt. +RP_Raumkategorie: + attributes: + besondererTyp: + doc: Klassifikation verschiedener besonderer Raumkategorien. + typ: + doc: Klassifikation verschiedener Raumkategorien. + doc: Raumkategorien sind nach bestimmten Kriterien abgegrenze Gebiete, in denen + vergleichbare Strukturen bestehen und in denen die Raumordnung gleichartige Ziele + verfolgt. Kriterien können z.B. siedlungsstrukturell, qualitativ oder potentialorientiert + sein. +RP_Rohstoff: + attributes: + bergbauplanungTyp: + doc: Klassifikation von Bergbauplanungstypen. + detaillierterRohstoffTyp: + doc: Abgebauer Rohstoff in Textform + folgenutzung: + doc: Klassifikation von Folgenutzungen bestimmter bergbaulicher Maßnahmen. + folgenutzungText: + doc: Textliche Festlegungen und Spezifizierungen zur Folgenutzung einer Bergbauplanung. + istAufschuettungAblagerung: + doc: Gibt an, ob der Rohstoff aus einer Aufschüttung oder Ablagerung gewonnen + wird + rohstoffTyp: + doc: Abgebauter Rohstoff. + tiefe: + doc: Tiefe eines Rohstoffes + zeitstufe: + doc: Zeitstufe des Rohstoffabbaus. + zeitstufeText: + doc: Textliche Spezifizierung einer Rohstoffzeitstufe, zum Beispiel kurzfristiger + Abbau (Zeitstufe I) und langfristige Sicherung für mindestens 25-30 Jahre + (Zeitstufe II). + doc: Rohstoff, inklusive Rohstoffprospektion, Rohstoffsicherung, Rohstoffabbau, + Bergbau und Bergbaufolgelandschaft. +RP_Schienenverkehr: + attributes: + besondererTyp: + doc: Klassifikation von besonderer Schienenverkehr-Infrastruktur. + typ: + doc: Klassifikation von Schienenverkehr-Infrastruktur. + doc: Schienenverkehr-Infrastruktur. +RP_Siedlung: + attributes: + bauhoehenbeschraenkung: + doc: Assoziierte Bauhöhenbeschränkung. + istSiedlungsbeschraenkung: + doc: Abfrage, ob der FeatureType eine Siedlungsbeschränkung ist. + doc: Allgemeines Siedlungsobjekt. Dieses vererbt an mehrere Spezialisierungen, ist + aber selbst nicht abstrakt. +RP_SonstVerkehr: + attributes: + typ: + doc: Sonstige Klassifikation von Verkehrs-Infrastruktur. + doc: Sonstige Verkehrsinfrastruktur, die sich nicht eindeutig einem anderen Typ + zuordnen lässt. +RP_SonstigeInfrastruktur: + attributes: {} + doc: Sonstige Infrastruktur. +RP_SonstigerFreiraumschutz: + attributes: {} + doc: Sonstiger Freiraumschutz. Nicht anderweitig zuzuordnende Freiraumstrukturen. +RP_SonstigerSiedlungsbereich: + attributes: {} + doc: Sonstiger Siedlungsbereich. +RP_SozialeInfrastruktur: + attributes: + typ: + doc: Klassifikation von Sozialer Infrastruktur. + doc: Soziale Infrastruktur ist ein (unpräziser) Sammelbegriff für Einrichtungen, + Leistungen und Dienste in den Kommunen, distinkt von Verkehr, Energieversorgung + und Entsorgung. Sie umfasst u.a. Bildung, Gesundheit, Sozialeinrichtungen, Kultureinrichtungen + und Einrichtungen der öffentlichen Verwaltung. +RP_Sperrgebiet: + attributes: + typ: + doc: Klassifikation verschiedener Sperrgebiettypen. + doc: Sperrgebiet, Gelände oder Areal, das für die Zivilbevölkerung überhaupt nicht + oder zeitweise nicht zugänglich ist. +RP_Sportanlage: + attributes: + typ: + doc: Klassifikation von Sportanlagen. + doc: "Sportanlagen und -bereiche.\r\n" +RP_Strassenverkehr: + attributes: + besondererTyp: + doc: Klassifikation von besonderer Strassenverkehr-Infrastruktur. + typ: + doc: Klassifikation von Strassenverkehr-Infrastruktur. + doc: Strassenverkehr-Infrastruktur. +RP_Verkehr: + attributes: + allgemeinerTyp: + doc: Allgemeine Klassifikation der Verkehrs-Arten. + bezeichnung: + doc: Bezeichnung eines Verkehrstyps. + status: + doc: Klassifikation von Verkehrsstati. + doc: Enthält allgemeine Verkehrs-Infrastruktur, die auch multiple Typen (etwa Straße + und Schiene) beinhalten kann. Die Klasse selbst vererbt an spezialisierte Verkehrsarten, + ist aber nicht abstrakt (d.h. sie kann selbst auch verwendet werden). +RP_Wasserschutz: + attributes: + typ: + doc: Klassifikation des Wasserschutztyps. + zone: + doc: Wasserschutzzone + doc: Grund-, Trink- und Oberflächenwasserschutz. +RP_Wasserverkehr: + attributes: + typ: + doc: Klassifikation von Wasserverkehr-Infrastruktur. + doc: Wasserverkehr-Infrastruktur. +RP_Wasserwirtschaft: + attributes: + typ: + doc: Klassifikation von Anlagen und Einrichtungen der Wasserwirtschaft + doc: Wasserwirtschaft beinhaltet die zielbewusste Ordnung aller menschlichen Einwirkungen + auf das ober- und unterirdische Wasser. Im Datenschema werden Gewässer, Wasserschutz, + Hochwasserschutz und Wasserverkehr in gesonderten Klassen gehalten. +RP_WohnenSiedlung: + attributes: + typ: + doc: Klassifikation von Wohnen- und Siedlungstypen. + doc: Wohn- und Siedlungsstruktur und -funktionen. +RP_ZentralerOrt: + attributes: + sonstigerTyp: + doc: Sonstige Klassifikation von Zentralen Orten. + typ: + doc: Klassifikation von Zentralen Orten. + doc: Zentrale Orte übernehmen die Versorgung ihrer Einwohner sowie Versorgungs- + und Entwicklungsfunktionen für den Einzugsbereich des Zentralen Ortes. Das zentralörtliche + System ist hierarchisch gegliedert. +SO_Baubeschraenkung: + attributes: + artDerFestlegung: + doc: Klassifizierung des Bauverbots bzw. der Baubeschränkung + detailArtDerFestlegung: + doc: Detaillierte Klassifizierung des Bauverbots bzw. der Baubeschränkung über + eine Codeliste + name: + doc: Informelle bezeichnung der Festlegung + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung + rechtlicheGrundlage: + doc: Rechtliche Grundlage des Bauverbots bzw. der Baubeschränkung + doc: Bereich, in denen Verbote oder Beschränkungen für die Errichtung baulicher + Anlagen bestehen +SO_Bereich: + attributes: + gehoertZuPlan: + doc: Referenz auf den Plan, zu dem der Bereich gehört + doc: "Bereich eines sonstigen raumbezogenen Plans.\r\n" +SO_Bodenschutzrecht: + attributes: + artDerFestlegung: + doc: Klassifizierung der Festlegung. + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere Klassifizierung der Festlegung. + istVerdachtsflaeche: + doc: Angabe ob es sich um eine Verdachtsfläche handelt. + name: + doc: Informelle Bezeichnung der Festlegung. + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung. bzw. Nummer in einem + Altlast-Kataster + doc: "Festlegung nach Bodenschutzrecht.\r\n" +SO_Denkmalschutzrecht: + attributes: + artDerFestlegung: + doc: Rechtliche Klassifizierung der Festlegung + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere rechtliche Klassifizierung + der Festlegung. + name: + doc: Informelle Bezeichnung der Festlegung + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung. + weltkulturerbe: + doc: Gibt an, ob das geschützte Objekt zum Weltkulturerbe gehört. + doc: "Festlegung nach Denkmalschutzrecht\r\n" +SO_Flaechenobjekt: + attributes: + flaechenschluss: + doc: 'Zeigt an, ob das Objekt als Flächenschlussobjekt oder Überlagerungsobjekt + gebildet werden soll. Flächenschlussobjekte dürfen sich nicht überlappen, + sondern nur an den Flächenrändern berühren, wobei die jeweiligen Stützpunkte + der Randkurven übereinander liegen müssen. Die Vereinigung der Flächenschlussobjekte + überdeckt den Geltungsbereich des sonstigen raumbezogenen Plans vollständig. ' + position: + doc: 'Flächenhafter Raumbezug des Objektes (Eine Einzelfläche oder eine Menge + von Flächen, die sich nicht überlappen dürfen). ' + doc: Basisklasse für alle Objekte mit flächenhaftem Raumbezug (eine Einzelfläche + oder eine Menge von Flächen, die sich nicht überlappen dürfen). +SO_Forstrecht: + attributes: + artDerFestlegung: + doc: Klassifizierung der Eigentumsart des Waldes. + betreten: + doc: Festlegung zusätzlicher, normalerweise nicht-gestatteter Aktivitäten, die + in dem Wald ausgeführt werden dürfen, nach §14 Abs. 2 Bundeswaldgesetz. + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere Klassifizierung der Eigentumsart + des Waldes + funktion: + doc: Klassifizierung der Fukktion des Waldes + name: + doc: Informelle Bezeichnung der Festlegung + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung + doc: Festlegung nach Forstrecht +SO_Gebiet: + attributes: + aufstellungsbeschhlussDatum: + doc: Datum des Aufstellungsbeschlusses + durchfuehrungEndDatum: + doc: End-Datum der Durchführung + durchfuehrungStartDatum: + doc: Start-Datum der Durchführung + gebietsArt: + doc: Klassifikation des Gebietes nach BauGB. + gemeinde: + doc: Zuständige Gemeinde + rechtsstandGebiet: + doc: Rechtsstand der Gebietsausweisung + sonstGebietsArt: + doc: Über eine Codeliste definierte Klassifikation einer nicht auf dem BauGB + beruhenden, z.B. länderspezifischen Gebietsausweisung. In dem Fall muss das + Attribut "gebietsArt" den Wert 9999 (Sonstiges) haben. + sonstRechtsstandGebiet: + doc: Über eine Codeliste definierter sonstiger Rechtsstand der Gebietsausweisung, + der nicht durch die Liste SO_RechtsstandGebietTyp wiedergegeben werden kann. + Das Attribut "rechtsstandGebiet" muss in diesem Fall den Wert 9999 (Sonstiges) + haben. + traegerMassnahme: + doc: Maßnahmen-Träger + doc: "Umgrenzung eines sonstigen Gebietes nach BauGB\r\n" +SO_Gelaendemorphologie: + attributes: + artDerFestlegung: + doc: Klassifikation der Geländestruktur + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere Klassifikation der Geländestruktur + name: + doc: Informelle Bezeichnung der Struktur + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Struktur + doc: Das Landschaftsbild prägende Geländestruktur +SO_Geometrieobjekt: + attributes: + flaechenschluss: + doc: 'Zeigt bei flächenhaftem Raumbezug an, ob das Objekt als Flächenschlussobjekt + oder Überlagerungsobjekt gebildet werden soll. Flächenschlussobjekte dürfen + sich nicht überlappen, sondern nur an den Flächenrändern berühren, wobei die + jeweiligen Stützpunkte der Randkurven übereinander liegen müssen. Die Vereinigung + der Flächenschlussobjekte überdeckt den Geltungsbereich des sonstigen raumbezogenen + Plans vollständig. ' + flussrichtung: + doc: "Das Attribut ist nur relevant, wenn ein Geometrieobjekt einen linienhaften\ + \ Raumbezug hat. Ist es mit dem Wert true belegt, wird damit ausgedrückt,\ + \ dass der Linie eine Flussrichtung in Digitalisierungsrichtung zugeordnet\ + \ ist. In diesem Fall darf bei Im- und Export die Digitalisierungsreihenfolge\ + \ der Stützpunkte nicht geändert werden. Wie eine definierte Flussrichtung\ + \ zu interpretieren oder bei einer Plandarstellung zu visualisieren ist,\ + \ bleibt der Implementierung überlassen.\r\nIst der Attributwert false oder\ + \ das Attribut nicht belegt, ist die Digitalisierungsreihenfolge der Stützpunkte\ + \ irrelevant." + nordwinkel: + doc: Orientierung des Objektes bei punktförmigem Raumbezug als Winkel gegen + die Nordrichtung. Zählweise im geographischen Sinn (von Nord über Ost nach + Süd und West) + position: + doc: Raumbezug - Entweder punktförmig, linienförmig oder flächenhaft, gemischte + Geometrie ist nicht zugelassen. + doc: Basisklasse für alle Objekte mit variablem Raumbezug. Ein konkretes Objekt + muss entweder punktförmigen, linienförmigen oder flächenhaften Raumbezug haben, + gemischte Geometrie ist nicht zugelassen. +SO_Gewaesser: + attributes: + artDerFestlegung: + doc: Klassifizierung des Gewässers + name: + doc: Informelle Bezeichnung dees Gewässers + nummer: + doc: Amtliche Bezeichnung / Kennziffer des Gewässers. + doc: 'Planartübergreifende Klasse zur Abbildung von Gewässern ' +SO_Grenze: + attributes: + sonstTyp: + doc: Über eine Codeliste definierte weitere Grenztypen, wenn das Attribut typ + den Wert 9999 hat. + typ: + doc: Typ der Grenze + doc: Grenze einer Verwaltungseinheit oder sonstige Grenze in raumbezogenen Plänen. +SO_KomplexeFestlegungGewaesser: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche + aufschrift: + doc: Aufschrift + detail: + doc: Über eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + näher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en). + doc: Spezifikation der Zweckbestimmung der Fläche +SO_KomplexeZweckbestStrassenverkehr: + attributes: + allgemein: + doc: Allgemeine Zweckbestimmung der Fläche oder Anlage + aufschrift: + doc: Aufschrift + detail: + doc: ber eine Codeliste definierte Zweckbestimmungen, die die allgemeine Zweckbestimmung + nher detaillieren. + textlicheErgaenzung: + doc: Textliche Ergänzung der spezifizierten Zweckbestimmung(en) + doc: Spezifikation der Zweckbestimmung einer Fläche oder Anlage für den Strassenverkehr +SO_Linienobjekt: + attributes: + position: + doc: Linienförmiger Raumbezug (Einzelne zusammenhängende Kurve, die aus Linienstücken + und Kreisbögen aufgebaut ist, oder eine Menge derartiger Kurven), + doc: Basisklasse für Objekte mit linienförmigem Raumbezug (eine einzelne zusammenhängende + Kurve, die aus Linienstücken und Kreisbögen zusammengesetzt sein kann, oder eine + Menge derartiger Kurven). +SO_Luftverkehrsrecht: + attributes: + artDerFestlegung: + doc: Rechtliche Klassifizierung der Festlegung. + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere Klassifizierung der Festlegung. + laermschutzzone: + doc: Lärmschutzzone nach LuftVG. + name: + doc: Informelle Bezeichnung der Festlegung + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung + doc: "Festlegung nach Luftverkehrsrecht.\r\n" +SO_Objekt: + attributes: + sonstRechtscharakter: + doc: Klassifizierung des Rechtscharakters wenn das Attribut "rechtscharakter" + den Wert "Sonstiges" (9999) hat. + doc: Basisklasse für die Inhalte sonstiger raumbezogener Planwerke ,von Klassen + zur Modellierung nachrichtlicher Übernahmen, sowie Planart-übergreifende Planinhalte. +SO_Plan: + attributes: + bereich: + doc: Referenz auf einen Bereich des sonstigen raumbezogenen Plans. + gemeinde: + doc: Zuständige Gemeinde + planArt: + doc: Über eine Codeliste definierter Typ des Plans. + planaufstellendeGemeinde: + doc: Die für die ursprüngliche Planaufstellung zuständige Gemeinde, falls diese + nicht unter dem Attribut gemeinde aufgeführt ist. Dies kann z.B. nach Gemeindefusionen + der Fall sein. + plangeber: + doc: "Für den Plan zuständige Stelle.\r\n" + versionBauGBDatum: + doc: Bekanntmachungs-Datum der zugrunde liegenden Version des BauGB. + versionBauGBText: + doc: Textliche Spezifikation der zugrunde liegenden Version des BauGB. + versionSonstRechtsgrundlageDatum: + doc: Bekanntmachungs-Datum einer zugrunde liegenden anderen Rechtsgrundlage + als das BauGB. + versionSonstRechtsgrundlageText: + doc: Textliche Spezifikation einer zugrunde liegenden anderen Rechtsgrundlage + als das BauGB. + doc: Klasse für sonstige, z. B. länderspezifische raumbezogene Planwerke. +SO_Punktobjekt: + attributes: + nordwinkel: + doc: Orientierung des Punktobjektes als Winkel gegen die Nordrichtung. Zählweise + im geographischen Sinn (von Nord über Ost nach Süd und West). + position: + doc: Punktförmiger Raumbezug (Einzelpunkt oder Punktmenge). + doc: Basisklasse für Objekte mit punktförmigem Raumbezug (Einzelpunkt oder Punktmenge). +SO_Schienenverkehrsrecht: + attributes: + artDerFestlegung: + doc: Rechtliche Klassifizierung der Festlegung + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere rechtliche Klassifizierung + der Festlegung. + name: + doc: Informelle Bezeichnung der Festlegung. + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung. + doc: Festlegung nach Schienenverkehrsrecht. +SO_SchutzgebietWasserrecht: + attributes: + artDerFestlegung: + doc: Klassifizierung des Schutzgebietes + detailArtDerFestlegung: + doc: Über eine Codelieste definierte detailliertere Klassifizierung + name: + doc: Informelle Bezeichnung des Gebiets + nummer: + doc: Amtliche Bezeichnung / Kennziffer des Gebiets. + zone: + doc: Klassifizierung der Schutzzone + doc: "Schutzgebiet nach WasserSchutzGesetz (WSG) bzw. HeilQuellenSchutzGesetz (HQSG).\r\ + \n" +SO_Sichtflaeche: + attributes: + art: + doc: Klassifikation der Einmündung einer untergeordneten auf eine übergeordnete + Straße gemäß den "Richtlinien für die Anlage von Stadtstraßen" (TAST 06) + geschwindigkeit: + doc: Zulässige Geschwindigkeit in der übergeordneten Straße, im km/h + knotenpunkt: + doc: Klassifikation des Knotenpunktes, dem die Sichtfläche zugeordnet ist + schenkellaenge: + doc: Schenkellänge des Sichtdreiecks gemäß RAST 06 + doc: Flächenhafte Festlegung einer Sichtfläche bzw. eines Sichtdreiecks +SO_SonstigesRecht: + attributes: + artDerFestlegung: + doc: Rechtliche Klassifizierung der Festlegung + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere rechtliche Klassifizierung + der Festlegung. + name: + doc: Informelle Bezeichnung der Festlegung + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung + doc: "Sonstige Festlegung.\r\n" +SO_Strassenverkehr: + attributes: + BM: + doc: Maximal zulässige Baumasse. + BMZ: + doc: Maximal zulässige Baumassenzahl. + BMZ_Ausn: + doc: Ausnahmsweise maximal zulässige Baumassenzahl. + BM_Ausn: + doc: Ausnahmsweise maximal zulässige Baumasse. + Bmax: + doc: Maximale Breite von Baugrundstücken. + Bmin: + doc: Minimale Breite von Baugrundstücken + Fmax: + doc: Höchstmaß für die Größe (Fläche) eines Baugrundstücks. + Fmin: + doc: Mindestmaß für die Größe (Fläche) eines Baugrundstücks. + GF: + doc: Maximal zulässige Geschossfläche. + GFZ: + doc: Maximal zulässige Geschossflächenzahl. + GFZ_Ausn: + doc: Maximal zulässige Geschossflächenzahl als Ausnahme. + GFZmax: + doc: Maximal zulässige Geschossflächenzahl bei einer Bereichsangabe. Das Attribut + GFZmin muss ebenfalls belegt sein. + GFZmin: + doc: Minimal zulässige Geschossflächenzahl . + GF_Ausn: + doc: Ausnahmsweise maximal zulässige Geschossfläche. + GFmax: + doc: Maximal zulässige Geschossfläche bei einer Bereichsabgabe. Das Attribut + GFmin muss ebenfalls belegt sein. + GFmin: + doc: Minimal zulässige Geschossfläche + GR: + doc: Maximal zulässige Grundfläche. + GRZ: + doc: Maximal zulässige Grundflächenzahl + GRZ_Ausn: + doc: Ausnahmsweise maximal zulässige Grundflächenzahl. + GRZmax: + doc: Maximal zulässige Grundflächenzahl bei einer Bereichsangabe. Das Attribut + GRZmin muss ebenfalls spezifiziert werden. + GRZmin: + doc: Minimal zulässige Grundflächenzahl. + GR_Ausn: + doc: Ausnahmsweise maximal zulässige Grundfläche. + GRmax: + doc: Maximal zulässige Grundfläche bei einer Bereichsangabe. Das Attribut GRmin + muss ebenfalls spezifiziert werden. + GRmin: + doc: Minimal zulässige Grundfläche. + MZspezial: + doc: Textuelle Spezifikation von speziellen Maßzahlen, wie z.B. einer GRZ in + Abhängigkeit von der Bebauungsart. + MaxZahlWohnungen: + doc: Höchstzulässige Zahl der Wohnungen in Wohngebäuden + MinGRWohneinheit: + doc: Minimale Größe eines Grundstücks pro Wohneinheit + Tmax: + doc: Maximale Tiefe von Baugrundstücken. + Tmin: + doc: Minimale Tiefe von Baugrundstücken. + Z: + doc: Maximalzahl der oberirdischen Vollgeschosse. + ZU: + doc: Maximal zulässige Zahl der unterirdischen Geschosse. + ZU_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der unterirdischen Geschosse. + ZUmax: + doc: Maximal zulässige Zahl der unterirdischen Geschosse bei einer Bereichsangabe. + Das Attribut ZUmin muss ebenfalls belegt sein. + ZUmin: + doc: Minimal zulässige Zahl der unterirdischen Geschosse. + ZUzwingend: + doc: Zwingend vorgeschriebene Zahl der unterirdischen Geschosse. + Z_Ausn: + doc: Ausnahmsweise maximal zulässige Zahl der oberirdischen Vollgeschosse. + Z_Dach: + doc: Maximalzahl der zusätzlich erlaubten Dachgeschosse, die gleichzeitig Vollgeschosse + sind. + Z_Staffel: + doc: Maximale Anzahl von oberirdisch zurückgesetzten Vollgeschossen als zusätzliche + Staffelgeschosse + Zmax: + doc: Maximal zulässige Zahl der oberirdischen Vollgeschosse bei einer Bereichsangabe. + Das Attribut Zmin muss ebenfalls belegt sein. + Zmin: + doc: Minimal zulässige Zahl der oberirdischen Vollgeschosse. + Zzwingend: + doc: Zwingend vorgeschriebene Zahl der oberirdischen Vollgeschosse. + artDerFestlegung: + doc: Zweckbestimmung der Straßenverkehrsfläche oder Anlage + begrenzungslinie: + doc: Referenz auf eine Linie, die die Straßenverkehrsfläche begrenzt. + einteilung: + doc: Straßeneinteilung nach Bundes-Fernstraßengesetz + hatDarstellungMitBesondZweckbest: + doc: Bei true wird ein flächenhaftes Planobjekt gemäß § 6.3 PlanZV dargestellt + ("Verkehrsflächen besonderer Zweckbestimmung"), und bei false gemäß § 6.1 + PlanZV ("Straßenverkehrsflächen"). + istOrtsdurchfahrt: + doc: Gibt an, ob es sich um eine Ortsdurchfahrt handelt + name: + doc: Informelle Bezeichnung der Festlegung. + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung. + nutzungsform: + doc: Nutzungsform der Fläche / Anlage + zugunstenVon: + doc: Begünstigter der Ausweisung + doc: 'Verkehrsfläche besonderer Zweckbestimmung (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB), + Darstellung von Flächen für den überörtlichen Verkehr und für die örtlichen Hauptverkehrszüge + ( §5, Abs. 2, Nr. 3 BauGB) sowie Festlegung nach Straßenverkehrsrecht. ' +SO_TextAbschnittFlaeche: + attributes: {} + doc: Bereich, in dem bestimmte textlich spezifizierte Planinhalte gültig sind, die + über die Relation " +SO_Wasserrecht: + attributes: + artDerFestlegung: + doc: Rechtliche Klassifizierung der Festlegung. + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere rechtliche Klassifizierung + der Festlegung. + istNatuerlichesUberschwemmungsgebiet: + doc: Gibt an, ob es sich bei der Fläche um ein natürliches Überschwemmungsgebiet + handelt. + name: + doc: Informelle Bezeichnung der Festlegung + nummer: + doc: Amtliche Bezeichnung / Kennziffer der Festlegung + doc: Festlegung nach Wasserhaushaltsgesetz (WHG) +SO_Wasserwirtschaft: + attributes: + artDerFestlegung: + doc: Klassifizierung der Festlegung + detailArtDerFestlegung: + doc: Über eine Codeliste definierte detailliertere rechtliche Klassifizierung + der Festlegung. + doc: Flächen für die Wasserwirtschaft, sowie Flächen für Hochwasserschutzanlagen + und für die Regelung des Wasserabflusses (§9 Abs. 1 Nr. 16a und 16b BauGB, §5 + Abs. 2 Nr. 7 BauGB). +XP_AbstraktesPraesentationsobjekt: + attributes: + art: + doc: "\"art\" gibt die Namen der Attribute an, die mit dem Präsentationsobjekt\ + \ dargestellt werden sollen. Dabei ist beim Verweis auf komplexe Attribute\ + \ des Fachobjekts die Xpath-Syntax zu verwenden. Wenn das zugehörige Attribut\ + \ oder Sub-Attribut des Fachobjekts mehrfach belegt ist, muss die []-Syntax\ + \ zur Spezifikation des zugehörigen Instanz-Attributs benutzt werden. \r\n\ + \r\nDas Attribut 'art' darf nur bei \"Freien Präsentationsobjekten\" (dientZurDarstellungVon\ + \ = NULL) nicht belegt sein." + darstellungsprioritaet: + doc: Enthält die Darstellungspriorität für Elemente der Signatur. Eine vom Standardwert + abweichende Priorität wird über dieses Attribut definiert und nicht über eine + neue Signatur. + dientZurDarstellungVon: + doc: Verweis auf das Fachobjekt, deren Plandarstellung durch das Präsentationsobjekt + unterstützt werden soll. + gehoertZuBereich: + doc: Referenz auf den Bereich, zu dem das Präsentationsobjekt gehört. + stylesheetId: + doc: Das Attribut "stylesheetId" zeigt auf ein extern definiertes Stylesheet, + das Parameter zur Visualisierung von Flächen, Linien, Punkten und Texten enthält. + Jedem Stylesheet ist weiterhin eine Darstellungspriorität zugeordnet. Außerdem + kann ein Stylesheet logische Elemente enthalten, die die Visualisierung abhängig + machen vom Wert des durch "art" definierten Attributes des Fachobjektes, das + durch die Relation "dientZurDarstellungVon" referiert wird. + doc: 'Abstrakte Basisklasse für alle Präsentationsobjekte. Die Attribute entsprechen + dem ALKIS-Objekt AP_GPO, wobei das Attribut "signaturnummer" in ' +XP_BegruendungAbschnitt: + attributes: + refText: + doc: Referenz auf ein externes Dokument das den Begründungs-Abschnitt enthält. + schluessel: + doc: Schlüssel zur Referenzierung des Abschnitts von einem Fachobjekt aus. + text: + doc: Inhalt eines Abschnitts der Begründung. + doc: Ein Abschnitt der Begründung des Plans. +XP_Bereich: + attributes: + aendertPlan: + doc: "Verweis auf einen anderen Plan, der durch den vorliegenden Planbereich\ + \ geändert wird.\r\n\r\nDies Konzept befindet sich noch in der Erprobung.\ + \ Es wird in nachfolgenden Versionen des Standards voraussichtlich geändert\ + \ oder erweitert." + aendertPlanBereich: + doc: "Verweis auf einen Planbereich eines anderen Plans, der durch den vorliegenden\ + \ Planbereich geändert wird.\r\n\r\nDies Konzept befindet sich noch in der\ + \ Erprobung. Es wird in nachfolgenden Versionen des Standards voraussichtlich\ + \ geändert oder erweitert." + bedeutung: + doc: Spezifikation der semantischen Bedeutung eines Bereiches. + detaillierteBedeutung: + doc: Detaillierte Erklärung der semantischen Bedeutung eines Bereiches, in Ergänzung + des Attributs "bedeutung". + erstellungsMassstab: + doc: Der bei der Erstellung der Inhalte des Bereichs benutzte Kartenmaßstab. + Wenn dieses Attribut nicht spezifiziert ist, gilt für den Bereich der auf + Planebene (XP_Plan) spezifizierte Maßstab. + geltungsbereich: + doc: Räumliche Abgrenzung des Bereiches. Wenn dieses Attribut nicht spezifiziert + ist, gilt für den Bereich der auf Planebene (XP_Plan) spezifizierte Geltungsbereich. + name: + doc: Bezeichnung des Bereiches + nummer: + doc: Nummer des Bereichs. Bereichsnummern beginnen standardmäßig mit 0. + planinhalt: + doc: Verweis auf einen Planinhalt des Bereichs + praesentationsobjekt: + doc: Referenz auf ein Präsentationsbereich, das zum Bereich gehört. + refScan: + doc: "Referenz auf einen georeferenzierte Rasterplan, der die Inhalte des Bereichs\ + \ wiedergibt. Das über refScan referierte Rasterbild zeigt einen Plan, dessen\ + \ Geltungsbereich durch den Geltungsbereich des Bereiches (Attribut geltungsbereich\ + \ von XP_Bereich) oder, wenn geltungsbereich nicht belegt ist, den Geltungsbereich\ + \ des Gesamtplans (Attribut raeumlicherGeltungsbereich von XP_Plan) definiert\ + \ ist. \r\n\r\nIm Standard sind nur georeferenzierte Rasterpläne zugelassen.\ + \ Die über refScan referierte externe Referenz muss deshalb entweder von der\ + \ Art \"PlanMitGeoreferenz\" sein oder die URL eines Geodienstes enthalten." + texte: + doc: Referenz auf einen textlich formulierten Planinhalt. + wurdeGeaendertVonPlan: + doc: "Verweis auf einen anderen Plan, durch den der vorliegende Planbereich\ + \ geändert wurde.\r\n\r\nDies Konzept befindet sich noch in der Erprobung.\ + \ Es wird in nachfolgenden Versionen des Standards voraussichtlich geändert\ + \ oder erweitert." + wurdeGeaendertVonPlanBereich: + doc: "Verweis auf einen Planbereich eines anderen Plan, durch den der vorliegende\ + \ Planbereich geändert wurde.\r\n\r\nDies Konzept befindet sich noch in der\ + \ Erprobung. Es wird in nachfolgenden Versionen des Standards voraussichtlich\ + \ geändert oder erweitert." + doc: Abstrakte Oberklasse für die Modellierung von Bereichen. Ein Bereich fasst + die Inhalte eines Plans nach bestimmten Kriterien zusammen. +XP_DatumAttribut: + attributes: + wert: + doc: Attributwert + doc: "Generische Attribute vom Datentyp \"Datum\"\r\n" +XP_DoubleAttribut: + attributes: + wert: + doc: Attributwert + doc: "Generisches Attribut vom Datentyp \"Double\".\r\n" +XP_ExterneReferenz: + attributes: + art: + doc: 'Typisierung der referierten Dokumente: Beliebiges Dokument oder georeferenzierter + Plan.' + beschreibung: + doc: Beschreibung des referierten Dokuments + datum: + doc: Datum des referierten Dokuments + georefURL: + doc: Referenz auf eine Georeferenzierungs-Datei. Das Attribut ist nur relevant + bei Verweisen auf georeferenzierte Rasterbilder. Wenn der XPlanGML Datensatz + und das referierte Dokument in einem hierarchischen Ordnersystem gespeichert + sind, kann die URI auch einen relativen Pfad vom XPlanGML-Datensatz zum Dokument + enthalten. + referenzMimeType: + doc: Mime-Type des referierten Dokuments + referenzName: + doc: Name bzw. Titel des referierten Dokuments. Der Standardname ist "Unbekannt". + referenzURL: + doc: "URI des referierten Dokuments, über den auf das Dokument lesend zugegriffen\ + \ werden kann. Wenn der XPlanGML Datensatz und das referierte Dokument in\ + \ einem hierarchischen Ordnersystem gespeichert sind, kann die URI auch einen\ + \ relativen Pfad vom XPlanGML-Datensatz zum Dokument enthalten. \r\nStandardmaßig\ + \ wird der Wert des Attributes referenzName verwendet." + doc: Verweis auf ein extern gespeichertes Dokument oder einen extern gespeicherten, + georeferenzierten Plan. Einer der beiden Attribute " +XP_FPO: + attributes: + position: + doc: Zur Plandarstellung benutzte Flächengeometrie. + doc: "Flächenförmiges Präsentationsobjekt. Entspricht der ALKIS Objektklasse AP_FPO.\r\ + \n" +XP_Gemeinde: + attributes: + ags: + doc: Amtlicher Gemeindeschlüssel (früher Gemeinde-Kennziffer) + gemeindeName: + doc: Name der Gemeinde. + ortsteilName: + doc: Name des Ortsteils + rs: + doc: Regionalschlüssel + doc: Spezifikation einer für die Aufstellung des Plans zuständigen Gemeinde. +XP_GenerAttribut: + attributes: + name: + doc: Name des Generischen Attributs + doc: "Abstrakte Basisklasse für Generische Attribute.\r\n" +XP_GesetzlicheGrundlage: + attributes: + datum: + doc: Bekanntmachungs-Datum des Gesetzes + detail: + doc: Detaillierte Spezifikation der gesetzlichen Grundlage mit Angabe einer + Paragraphennummer + name: + doc: Name / Titel des Gesetzes + doc: Angeben zur Spezifikation der gesetzlichen Grundlage eines Planinhalts +XP_Hoehenangabe: + attributes: + abweichenderBezugspunkt: + doc: Textuelle Spezifikation eines Höhenbezugspunktes wenn das Attribut "bezugspunkt" + nicht belegt ist. + abweichenderHoehenbezug: + doc: Textuelle Spezifikation des Höhenbezuges wenn das Attribut "hoehenbezug" + nicht belegt ist. + bezugspunkt: + doc: Bestimmung des Bezugspunktes der Höhenangaben. Wenn weder dies Attribut + noch das Attribut "abweichenderBezugspunkt" belegt sind, soll die Höhenangabe + als vertikale Einschränkung des zugeordneten Planinhalts interpretiert werden. + bezugspunktText: + doc: 'Das Attribut spezifiziert oder ergänzt den Bezug des Höhenbezugspunktes + (insbesondere der Traufhöhe), z. B. als "bergseitig, "talseitig", "straßenseitig" + oder "gartenseitig". ' + h: + doc: Maximal zulässige Höhe des Bezugspunktes (bezugspunkt) . + hMax: + doc: 'Maximal zulässige Höhe des Bezugspunktes (bezugspunkt) bei einer Bereichsangabe, + bzw. obere Grenze des vertikalen Gültigkeitsbereiches eines Planinhalts, wenn + "bezugspunkt" nicht belegt ist. In diesem Fall gilt: Ist "hMin" nicht belegt, + gilt die Festlegung bis zur Höhe "hMax".' + hMin: + doc: 'Minimal zulässige Höhe des Bezugspunktes (bezugspunkt) bei einer Bereichsangabe, + bzw. untere Grenze des vertikalen Gültigkeitsbereiches eines Planinhalts, + wenn "bezugspunkt" nicht belegt ist. In diesem Fall gilt: Ist "hMax" nicht + belegt, gilt die Festlegung ab der Höhe "hMin".' + hZwingend: + doc: Zwingend einzuhaltende Höhe des Bezugspunktes (bezugspunkt) , bzw. Beschränkung + der vertikalen Gültigkeitsbereiches eines Planinhalts auf eine bestimmte Höhe. + hoehenbezug: + doc: "Art des Höhenbezuges.\r\n" + doc: Spezifikation einer Angabe zur vertikalen Höhe oder zu einem Bereich vertikaler + Höhen. Es ist möglich, spezifische Höhenangaben (z.B. die First- oder Traufhöhe + eines Gebäudes) vorzugeben oder einzuschränken, oder den Gültigkeitsbereich eines + Planinhalts auf eine bestimmte Höhe ( +XP_IntegerAttribut: + attributes: + wert: + doc: Attributwert + doc: "Generische Attribute vom Datentyp \"Integer\".\r\n" +XP_LPO: + attributes: + position: + doc: Zur Plandarstellung benutzte Liniengeometrie. + doc: "Linienförmiges Präsentationsobjekt. Entspricht der ALKIS Objektklasse AP_LPO.\r\ + \n" +XP_LTO: + attributes: + position: + doc: Linienführung des Textes + doc: "Textförmiges Präsentationsobjekt mit linienförmiger Textgeometrie. Entspricht\ + \ der ALKIS-Objektklasse AP_LTO.\r\n" +XP_Nutzungsschablone: + attributes: + spaltenAnz: + doc: Anzahl der Spalten in der Nutzungsschablone + zeilenAnz: + doc: Anzahl der Zeilen in der Nutzungsschablone + doc: "Modelliert eine Nutzungsschablone. Die darzustellenden Attributwerte werden\ + \ zeilenweise in die Nutzungsschablone geschrieben.\r\n" +XP_Objekt: + attributes: + aufschrift: + doc: Spezifischer Text zur Beschriftung von Planinhalten + ebene: + doc: Zuordnung des Objektes zu einer vertikalen Ebene. Der Standard-Ebene 0 + sind Objekte auf der Erdoberfläche zugeordnet. Nur unter diesen Objekten wird + der Flächenschluss hergestellt. Bei Plan-Objekten, die im wesentlichen unterhalb + der Erdoberfläche liegen (z.B. Tunnel), ist ebene < 0. Bei Objekten, die + im wesentlichen oberhalb der Erdoberfläche liegen (z.B. Festsetzungen auf + Brücken), ist ebene > 0. Zwischen Objekten auf Ebene 0 und einer Ebene <> + 0 muss nicht unbedingt eine (vollständige) physikalische Trennung bestehen. + endeBedingung: + doc: Notwendige Bedingung für das Ende der Wirksamkeit eines Planinhalts. + externeReferenz: + doc: Referenz auf ein Dokument oder einen georeferenzierten Rasterplan. + gehoertZuBereich: + doc: Verweis auf den Bereich, zu dem der Planinhalt gehört. Diese Relation sollte + immer belegt werden. In Version 6.0 wird sie in eine Pflicht-Relation umgewandelt + werden. + gesetzlicheGrundlage: + doc: Angabe der gesetzlichen Grundlage des Planinhalts. + gliederung1: + doc: ' Die „gliederung1“ beinhaltet bei einer Nummerierung der Baugebietsteilfläche + oder Baufläche die Gliederungszahl, unter Umständen zusätzlich ein Kürzel + für eingeschränkte Nutzungen, z.B. gliederung1=2 bei WA 2, gliederung1=-E + 3 bei GE-E 3; bei einer überlagernde Festlegung beinhaltet „gliederung1“ ein + Kürzel und eine Gliederungszahl, z.B. gliederung1 = AP 1 bei AP 1.' + gliederung2: + doc: 'Die `gliederung2` kommt bei weitergehenden, besonderen Festsetzungen für + Texteinschriebe mit Zahlen-, Buchstabencodes oder Paragrafenbezügen im Plan + zum Einsatz. Beispiele: (A), (A, B, G) oder Zone 1.' + hatGenerAttribut: + doc: Erweiterung des definierten Attributsatzes eines Objektes durch generische + Attribute. + hoehenangabe: + doc: Angaben zur vertikalen Lage und Höhe eines Planinhalts. + rechtscharakter: + doc: Rechtliche Charakterisierung eines Planinhalts + rechtsstand: + doc: "Angabe, ob der Planinhalt bereits besteht, geplant ist, oder zukünftig\ + \ wegfallen soll. \r\nDas Attribut sollte nur verwendet werden, wenn es für\ + \ die planerische Aussage notwendig ist. Eine generelle Belegung für jedes\ + \ Objekt ist nicht hilfreich." + refBegruendungInhalt: + doc: Referenz eines raumbezogenen Fachobjektes auf Teile der Begründung. + refTextInhalt: + doc: Referenz eines raumbezogenen Fachobjektes auf textuell formulierte Planinhalte. + startBedingung: + doc: Notwendige Bedingung für die Wirksamkeit eines Planinhalts. + text: + doc: Beliebiger Text + uuid: + doc: Eindeutiger Identifier des Objektes. + wirdDargestelltDurch: + doc: Verweis auf ein Präsentationsobjekt, das die Plandarstellung des Fachobjektes + unterstützen soll. + doc: Abstrakte Oberklasse für alle XPlanung-Fachobjekte. Die Attribute dieser Klasse + werden über den Vererbungs-Mechanismus an alle Fachobjekte weitergegeben. +XP_PPO: + attributes: + drehwinkel: + doc: Winkel um den der Text oder die Signatur mit punktförmiger Bezugsgeometrie + aus der Horizontalen gedreht ist, Angabe in Grad. Zählweise im mathematisch + positiven Sinn (von Ost über Nord nach West und Süd). + hat: + doc: Die Relation ermöglicht es, einem punktförmigen Präsentationsobjekt ein + linienförmiges Präsentationsobjekt zuzuweisen. Einziger bekannter Anwendungsfall + ist der Zuordnungspfeil eines Symbols oder einer Nutzungsschablone. + position: + doc: Position des zur Visualisierung benutzten Textes oder Symbols, + skalierung: + doc: Skalierungsfaktor für Symbole. + doc: "Punktförmiges Präsentationsobjekt. Entspricht der ALKIS-Objektklasse AP_PPO.\r\ + \n" +XP_PTO: + attributes: + drehwinkel: + doc: Winkel um den der Text oder die Signatur mit punktförmiger Bezugsgeometrie + aus der Horizontalen gedreht ist, Angabe in Grad. Zählweise im mathematisch + positiven Sinn (von Ost über Nord nach West und Süd). + position: + doc: Position des Textes + doc: "Textförmiges Präsentationsobjekt mit punktförmiger Festlegung der Textposition.\ + \ Entspricht der ALKIS-Objektklasse AP_PTO.\r\n" +XP_Plan: + attributes: + aendertPlan: + doc: Verweis auf einen anderen Plan, der durch den vorliegenden Plan geändert + wird. + aendertPlanBereich: + doc: "Verweis auf einen Planbereich eines anderen Plans, der durch den vorliegenden\ + \ Plan geändert wird.\r\n\r\nDies Konzept befindet sich noch in der Erprobung.\ + \ Es wird in nachfolgenden Versionen des Standards voraussichtlich geändert\ + \ oder erweitert." + begruendungsTexte: + doc: Referenz auf einen Abschnitt der Begründung. Diese Relation darf nicht + verwendet werden, wenn die Begründung als Gesamt-Dokument referiert werden + soll. In diesem Fall sollte über das Attribut externeReferenz eine Objekt + XP_SpezExterneReferent mit typ=1010 (Begruendung) verwendet werden. + beschreibung: + doc: Kommentierende Beschreibung des Plans. + bezugshoehe: + doc: Standard Bezugshöhe (absolut NhN) für relative Höhenangaben von Planinhalten. + erstellungsMassstab: + doc: Der bei der Erstellung des Plans benutzte Kartenmaßstab. + externeReferenz: + doc: 'Referenz auf ein Dokument, einen Datenbankeintrag oder einen georeferenzierten + Rasterplan. ' + genehmigungsDatum: + doc: Datum der Genehmigung des Plans + hatGenerAttribut: + doc: Erweiterung der vorgegebenen Attribute durch generische Attribute. + hoehenbezug: + doc: Bei Höhenangaben im Plan standardmäßig verwendeter Höhenbezug (z.B. Höhe + über NN). + internalId: + doc: Interner Identifikator des Plans. + kommentar: + doc: Beliebiger Kommentar zum Plan. + name: + doc: Name des Plans. + nummer: + doc: Nummer des Plans. + raeumlicherGeltungsbereich: + doc: Grenze des räumlichen Geltungsbereiches des Plans. + technHerstellDatum: + doc: Datum, an dem der Plan technisch ausgefertigt wurde. + technischerPlanersteller: + doc: Bezeichnung der Institution oder Firma, die den Plan technisch erstellt + hat. + texte: + doc: 'Referenz auf einen textlich formulierten Planinhalt. ' + untergangsDatum: + doc: Datum, an dem der Plan (z.B. durch Ratsbeschluss oder Gerichtsurteil) aufgehoben + oder für nichtig erklärt wurde. + verfahrensMerkmale: + doc: Vermerke der am Planungsverfahren beteiligten Akteure. + wurdeGeaendertVonPlan: + doc: Verweis auf einen anderen Plan, durch den der vorliegende Plan geändert + wurde. + wurdeGeaendertVonPlanBereich: + doc: "Verweis auf einen Planbereich eines anderen Plan, durch den der vorliegende\ + \ Plan geändert wurde.\r\n\r\nDies Konzept befindet sich noch in der Erprobung.\ + \ Es wird in nachfolgenden Versionen des Standards voraussichtlich geändert\ + \ oder erweitert." + doc: Abstrakte Oberklasse für alle Klassen raumbezogener Pläne. +XP_Plangeber: + attributes: + kennziffer: + doc: Kennziffer des Plangebers. + name: + doc: Name des Plangebers. + doc: Spezifikation der Institution, die für den Plan verantwortlich ist. +XP_Praesentationsobjekt: + attributes: {} + doc: "Entspricht der ALKIS-Objektklasse AP_Darstellung mit dem Unterschied, dass\ + \ auf das Attribut \"positionierungssregel\" verzichtet wurde. Die Klasse darf\ + \ nur als gebundenes Präsentationsobjekt verwendet werden. Die Standard-Darstellung\ + \ des verbundenen Fachobjekts wird dann durch die über stylesheetId spezifizierte\ + \ Darstellung ersetzt. Die Umsetzung dieses Konzeptes ist der Implementierung\ + \ überlassen.\r\n" +XP_SPEMassnahmenDaten: + attributes: + klassifizMassnahme: + doc: Klassifikation der Maßnahme + massnahmeKuerzel: + doc: Kürzel der durchzuführenden Maßnahme. + massnahmeText: + doc: Durchzuführende Maßnahme als freier Text. + doc: Spezifikation der Attribute für einer Schutz-, Pflege- oder Entwicklungsmaßnahme. +XP_SpezExterneReferenz: + attributes: + typ: + doc: Typ / Inhalt des referierten Dokuments oder Rasterplans. + doc: Ergänzung des Datentyps XP_ExterneReferenz um ein Attribut zur semantischen + Beschreibung des referierten Dokuments. +XP_StringAttribut: + attributes: + wert: + doc: Attributwert + doc: "Generisches Attribut vom Datentyp \"CharacterString\"\r\n" +XP_TPO: + attributes: + fontSperrung: + doc: Die Zeichensperrung steuert den zusätzlichen Raum, der zwischen 2 aufeinanderfolgende + Zeichenkörper geschoben wird. Er ist ein Faktor, der mit der angegebenen Zeichenhöhe + multipliziert wird, um den einzufügenden Zusatzabstand zu erhalten. Mit der + Abhängigkeit von der Zeichenhöhe wird erreicht, dass das Schriftbild unabhängig + von der Zeichenhöhe gleich wirkt. Der Defaultwert ist 0. + hat: + doc: Die Relation ermöglicht es, einem textlichen Präsentationsobjekt ein linienförmiges + Präsentationsobjekt zuzuweisen. Einziger bekannter Anwendungsfall ist der + Zuordnungspfeil eines Symbols oder einer Nutzungsschablone. + horizontaleAusrichtung: + doc: "Gibt die Ausrichtung des Textes bezüglich der Textgeometrie an.\r\nlinksbündig:\ + \ Der Text beginnt an der Punktgeometrie bzw. am Anfangspunkt der Liniengeometrie.\r\ + \nrechtsbündig: Der Text endet an der Punktgeometrie bzw. am Endpunkt der\ + \ Liniengeometrie\r\nzentrisch: Der Text erstreckt sich von der Punktgeometrie\ + \ gleich weit nach links und rechts bzw. steht auf der Mitte der Standlinie." + schriftinhalt: + doc: Schriftinhalt; enthält den darzustellenden Text. + skalierung: + doc: Skalierungsfaktor der Schriftgröße, bezogen auf die von der interpretierenden + Software festgelegte Standardschrift + vertikaleAusrichtung: + doc: Die vertikale Ausrichtung eines Textes gibt an, ob die Bezugsgeometrie + die Basis (Grundlinie) des Textes, die Mitte oder obere Buchstabenbegrenzung + betrifft. + doc: Abstrakte Oberklasse für textliche Präsentationsobjekte. Entspricht der ALKIS + Objektklasse AP_TPO +XP_TextAbschnitt: + attributes: + gesetzlicheGrundlage: + doc: Gesetzliche Grundlage des Text-Abschnittes + rechtscharakter: + doc: Rechtscharakter des textlich formulierten Planinhalts + refText: + doc: Referenz auf ein externes Dokument das den zugehörigen Textabschnitt enthält. + schluessel: + doc: Schlüssel zur Referenzierung des Abschnitts. + text: + doc: Inhalt eines Abschnitts der textlichen Planinhalte + doc: Ein Abschnitt der textlich formulierten Inhalte des Plans. +XP_URLAttribut: + attributes: + wert: + doc: Attributwert + doc: "Generische Attribute vom Datentyp \"URL\"\r\n" +XP_VerbundenerPlan: + attributes: + aenderungsArt: + doc: Spezifikation der Art der Änderungsbeziehung zwischen den verbundenen Plan- + bzw. Planbereichs-Objekten. + aenderungsdatum: + doc: Datum, an dem die Änderung in Kraft getreten ist. Das Attribut muss mit + dem Datum des Inkrafttretens des ändernden Plans konsistent sein. + nummer: + doc: Nummer (Attribut "nummer" von XP_Plan) des verbundenen Plans + planName: + doc: Name (Attribut "name" von XP_Plan) des verbundenen Plans. + verbundenerPlan: + doc: Referenz auf einen verbundenen Plan, der den aktuellen Plan oder Planbereich + ändert oder von ihm geändert wird. + doc: Spezifikation eines anderen Plans, der mit dem Ausgangsplan oder Planbereich + verbunden ist und diesen ändert bzw. von ihm geändert wird. +XP_VerbundenerPlanBereich: + attributes: + bereichNummer: + doc: Nummer (Attribut nummer von XP_Bereich) des verbundenen Planbereiches. + verbundenerPlanBereich: + doc: Referenz auf einen verbundenen Planbereich, der den aktuellen Plan oder + Planbereich ändert oder von ihm geändert wird. + doc: Spezifikation eines anderen Planbereichs, der mit einem Ausgangsplan oder Ausgangsbereich + verknüpft ist und diesen ändert bzw. von ihm geändert wird. +XP_VerfahrensMerkmal: + attributes: + datum: + doc: Datum des Vermerks + signatur: + doc: Unterschrift + signiert: + doc: Angabe, ob die Unterschrift erfolgt ist. + vermerk: + doc: Inhalt des Vermerks. + doc: "Vermerk eines am Planungsverfahren beteiligten Akteurs.\r\n" +XP_WirksamkeitBedingung: + attributes: + bedingung: + doc: Textlich formulierte Bedingung für die Wirksamkeit oder Unwirksamkeit einer + Festsetzung. + datumAbsolut: + doc: Datum an dem eine Festsetzung wirksam oder unwirksam wird. + datumRelativ: + doc: Zeitspanne, nach der eine Festsetzung wirksam oder unwirksam wird, wenn + die im Attribut bedingung spezifizierte Bedingung erfüllt ist. + doc: Spezifikation von Bedingungen für die Wirksamkeit oder Unwirksamkeit einer + Festsetzung. diff --git a/src/SAGisXPlanung/config/svg_config.yaml b/src/SAGisXPlanung/config/svg_config.yaml new file mode 100644 index 0000000..3f68640 --- /dev/null +++ b/src/SAGisXPlanung/config/svg_config.yaml @@ -0,0 +1,518 @@ + +#region BP_Bebauung + +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_1.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 1 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_2.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 2 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_3.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 3 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_4.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 4 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_5.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 5 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_6.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 6 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_7.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 7 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_8.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 8 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_9.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 9 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_10.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 10 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_11.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 11 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_12.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 12 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_13.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 13 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_14.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 14 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_15.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 15 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_16.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 16 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_17.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 17 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_18.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 18 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_19.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 19 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_20.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 20 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_21.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 21 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_22.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 22 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_23.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 23 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_24.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 24 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_25.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 25 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_26.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 26 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_27.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 27 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_28.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 28 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_29.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 29 +Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_30.svg: + category: BP_Bebauung + name: Zahl der Wohnungen 30 + +Dorfgebiet.svg: + category: BP_Bebauung + name: Dorfgebiet +Gewerbegebiet.svg: + category: BP_Bebauung + name: Gewerbegebiet +GewerblicheBauflaeche.svg: + category: BP_Bebauung + name: Gewerbliche Baufläche +Kerngebiet.svg: + category: BP_Bebauung + name: Kerngebiet +Mischgebiet.svg: + category: BP_Bebauung + name: Mischgebiet +Sondergebiet_Camping.svg: + category: BP_Bebauung + name: Sondergebiet Camping +Sondergebiet_Ferienhaus.svg: + category: BP_Bebauung + name: Sondergebiet Ferienhäuser +Sondergebiet_Woch.svg: + category: BP_Bebauung + name: Sondergebiet Wochenendhäuser + +Mass_der_baulichen_Nutzung_Baumasse_SW_Text.svg: + category: BP_Bebauung + name: Bezeichnung Baumasse +Mass_der_baulichen_Nutzung_Baumassenzahl_SW_Text.svg: + category: BP_Bebauung + name: Bezeichnung Baumassenzahl +Mass_der_baulichen_Nutzung_Geschossflaechenzahl_SW_Text.svg: + category: BP_Bebauung + name: Bezeichnung Geschossflächenzahl +Mass_der_baulichen_Nutzung_Grundflaeche_SW_Text.svg: + category: BP_Bebauung + name: Bezeichnung Grundfläche +Mass_der_baulichen_Nutzung_Grundflaechenzahl_SW_Text.svg: + category: BP_Bebauung + name: Bezeichnung Grundflächenzahl +Hoehe_baulicher_Anlagen_als_Hoechstmass_Firsthoehe_SW.svg: + category: BP_Bebauung + name: Bezeichnung Firsthöhe +Hoehe_baulicher_Anlagen_als_Hoechstmass_Firsthoehe_zwingend_Kreis_SW.svg: + category: BP_Bebauung + name: Bezeichnung Firsthöhe (zwingend) +Hoehe_baulicher_Anlagen_als_Hoechstmass_Oberkante_SW.svg: + category: BP_Bebauung + name: Bezeichnung Oberkante +Hoehe_baulicher_Anlagen_als_Hoechstmass_Oberkante_zwingend_Kreis_SW_1.svg: + category: BP_Bebauung + name: Bezeichnung Oberkante (zwingend) +Hoehe_baulicher_Anlagen_als_Hoechstmass_Traufhoehe_SW.svg: + category: BP_Bebauung + name: Bezeichnung Traufhöhe +Hoehe_baulicher_Anlagen_als_Hoechstmass_Traufhoehe_zwingend_Kreis_SW.svg: + category: BP_Bebauung + name: Bezeichnung Traufhöhe (zwingend) + +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_1.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 1 +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_2.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 2 +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_3.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 3 +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_4.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 4 +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_5.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 5 +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_6.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 6 +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_7.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 7 +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_8.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 8 +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_9.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 9 +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_10.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 10 +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_1.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 1 (zwingend) +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_2.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 2 (zwingend) +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_3.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 3 (zwingend) +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_4.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 4 (zwingend) +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_5.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 5 (zwingend) +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_6.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 6 (zwingend) +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_7.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 7 (zwingend) +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_8.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 8 (zwingend) +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_9.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 9 (zwingend) +Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_10.svg: + category: BP_Bebauung + name: Zahl der Vollgeschosse 10 (zwingend) + +Geschossflaechenzahl_Kreis_SW_0.1.svg: + category: BP_Bebauung + name: Geschossflächenzahl 0.1 +Geschossflaechenzahl_Kreis_SW_0.2.svg: + category: BP_Bebauung + name: Geschossflächenzahl 0.2 +Geschossflaechenzahl_Kreis_SW_0.3.svg: + category: BP_Bebauung + name: Geschossflächenzahl 0.3 +Geschossflaechenzahl_Kreis_SW_0.4.svg: + category: BP_Bebauung + name: Geschossflächenzahl 0.4 +Geschossflaechenzahl_Kreis_SW_0.5.svg: + category: BP_Bebauung + name: Geschossflächenzahl 0.5 +Geschossflaechenzahl_Kreis_SW_0.6.svg: + category: BP_Bebauung + name: Geschossflächenzahl 0.6 +Geschossflaechenzahl_Kreis_SW_0.7.svg: + category: BP_Bebauung + name: Geschossflächenzahl 0.7 +Geschossflaechenzahl_Kreis_SW_0.8.svg: + category: BP_Bebauung + name: Geschossflächenzahl 0.8 +Geschossflaechenzahl_Kreis_SW_0.9.svg: + category: BP_Bebauung + name: Geschossflächenzahl 0.9 +Geschossflaechenzahl_Kreis_SW_1.0.svg: + category: BP_Bebauung + name: Geschossflächenzahl 1.0 + +Bauweise_Geschlossene_Bauweise.svg: + category: BP_Bebauung + name: Geschlossene Bauweise +Bauweise_Offene_Bauweise.svg: + category: BP_Bebauung + name: Offene Bauweise +Bauweise_Nur_Doppelhaeuser_zulaessig.svg: + category: BP_Bebauung + name: Bauweise, nur Doppelhäuser +Bauweise_Nur_Einzel-_und_Doppelhaeuser_zulaessig.svg: + category: BP_Bebauung + name: Bauweise, Einzel- und Doppelhäuser +Bauweise_Nur_Einzelhaeuser_zulaessig.svg: + category: BP_Bebauung + name: Bauweise, nur Einzelhäuser +Bauweise_Nur_Hausgruppen_zulaessig.svg: + category: BP_Bebauung + name: Bauweise, nur Hausgruppen +# endregion + +# region BP_Gemeinbedarf_Spiel_und_Sportanlagen + +Allgemeinbildende_Schule.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Allgemeinbildende Schule +Alteneinrichtung.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Alteneinrichtung +Anlage_Spielanlage.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Spielanlage +Anlage_Sportanlage.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Sportanlage +Berufsbildende_Schule.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Berufsbildende Schule +Bildung_Forschung.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Bildung und Forschung +Einrichtung_Kultur.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Kulturelle Einrichtung +Einrichtung_Soziales.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Soziale Einrichtung +Feuerwehr.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Feuerwehr +Gemeindebehoerde.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Gemeindebehörde +Gericht.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Gericht +Hallenbad.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Hallenbad +Hochschule.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Hochschule +Jugendfreizeitheim.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Jugendfreizeitheim +Jugendheim.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Jugendheim +Justizvollzug.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Justizvollzug +Kindergarten.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Kindergarten +Kirche.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Kirche +Kirchliche_Einrichtung.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Kirchliche Einrichtung +Krankenhaus.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Krankenhaus +Kreisbehoerde.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Kreisbehoerde +Landesbehoerde.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Landesbehoerde +Museum.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Museum +Oeffentliche_Verwaltung.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Öffentliche Verwaltung +Parkplatz.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Parkplatz +Polizei.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Polizei +Post.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Post +Schule.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Schule +Schutzbauwerk.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Museum +Theater.svg: + category: BP_Gemeinbedarf_Spiel_und_Sportanlagen + name: Theater + +# endregion + +# region BP_Landwirtschaft_Wald_und_Gruenflaechen +Badeplatz.svg: + category: BP_Landwirtschaft_Wald_und_Gruenflaechen + name: Badeplatz +Dauerkleingärten.svg: + category: BP_Landwirtschaft_Wald_und_Gruenflaechen + name: Dauerkleingärten +Erholungswald_Kreis_SW.svg: + category: BP_Landwirtschaft_Wald_und_Gruenflaechen + name: Erholungswald +Parkanlage.svg: + category: BP_Landwirtschaft_Wald_und_Gruenflaechen + name: Parkanlage +Friedhof.svg: + category: BP_Landwirtschaft_Wald_und_Gruenflaechen + name: Friedhof +Spielplatz.svg: + category: BP_Landwirtschaft_Wald_und_Gruenflaechen + name: Spielplatz +Sportplatz.svg: + category: BP_Landwirtschaft_Wald_und_Gruenflaechen + name: Sportplatz +Zeltplatz.svg: + category: BP_Landwirtschaft_Wald_und_Gruenflaechen + name: Zeltplatz +# endregion + +# region BP_Naturschutz_Landschaftsbild_Naturhaushalt +Anpflanzen_Baum.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Anpflanzen, Bäume +Anpflanzen_Baum_SW.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Anpflanzen, Bäume (SW) +Anpflanzen_Sonstiges.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Anpflanzen, Sonstiges +Anpflanzen_Sonstiges_SW.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Anpflanzen, Sonstiges (SW) +Anpflanzen_Straeucher.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Anpflanzen, Sträucher +Anpflanzen_Straeucher_SW.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Anpflanzen, Sträucher (SW) +Erhaltung_Baum.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Erhaltung, Bäume +Erhaltung_Baum_SW.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Erhaltung, Bäume (SW) +Erhaltung_Sonstiges.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Erhaltung, Sonstiges +Erhaltung_Sonstiges_SW.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Erhaltung, Sonstiges (SW) +Erhaltung_Straeucher.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Erhaltung, Sträucher +Erhaltung_Straeucher_SW.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Erhaltung, Sträucher (SW) +Schutzgebiet_Geschuetzter_Landschaftsbestandteil_SW.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Geschützter Landschaftsbestandteil +Schutzgebiet_Landschaftsschutzgebiet_SW.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Landschaftsschutzgebiet +Schutzgebiet_Nationalpark_SW.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Nationalpark +Schutzgebiet_Naturdenkmal_SW.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Naturdenkmal +Schutzgebiet_Naturpark_SW.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Naturpark +Schutzgebiet_Naturschutzgebiet_SW.svg: + category: BP_Naturschutz_Landschaftsbild_Naturhaushalt + name: Naturschutzgebiet +# endregion + +# region BP_Ver_und_Entsorgung +Abfall.svg: + category: BP_Ver_und_Entsorgung + name: Abfallentsorgung +Ablagerung.svg: + category: BP_Ver_und_Entsorgung + name: Ablagerung +Abwasser.svg: + category: BP_Ver_und_Entsorgung + name: Abwasser +Elektrizitaet.svg: + category: BP_Ver_und_Entsorgung + name: Elektrizität +Erneuerbare_Energien.svg: + category: BP_Ver_und_Entsorgung + name: Erneuerbare Energien +Fernwaerme.svg: + category: BP_Ver_und_Entsorgung + name: Fernwärme +Gas.svg: + category: BP_Ver_und_Entsorgung + name: Gas +Kraft_Waerme_Kopplung.svg: + category: BP_Ver_und_Entsorgung + name: Fläche/Anlage für Kraft-Wärme Kopplung +Wasser.svg: + category: BP_Ver_und_Entsorgung + name: Wasser +# endregion + +# region BP_Verkehr +Fussgaengerbereich.svg: + category: BP_Verkehr + name: Fußgängerbereich +Parkierungsflaeche.svg: + category: BP_Verkehr + name: Fläche zum Parken von Fahrzeugen +VerkehrsberuhigterBereich.svg: + category: BP_Verkehr + name: Verkehrsberuhigte Zone +# endregion + +# region BP_Wasser +Hafen.svg: + category: BP_Wasser + name: Hafen +Hochwasserrueckhaltebecken.svg: + category: BP_Wasser + name: Hochwasser-Rückhaltebecken +Schutzgebiet_fuer_Grund-_und_Quellwassergewinnung.svg: + category: BP_Wasser + name: Schutzgebiet Grund- oder Quellwasser +Schutzgebiet_fuer_Oberflaechenwasser.svg: + category: BP_Wasser + name: Schutzgebiet Oberflächenwasser +Ueberschwaemmungsgebiet.svg: + category: BP_Wasser + name: Überschwemmungsgefährdetes Gebiet +# endregion + +# region SO_SonstigeGebiete +Einzelanlagen_die_dem_Denkmalschutz_unterliegen_SW.svg: + category: SO_SonstigeGebiete + name: Denkmalschutz Einzelanlage +# endregion + +# region Sonstiges +Kennzeichnung_derLage_ohne_Flaechendarstellung.svg: + category: Sonstiges + name: Lage ohne Flächendarstellung +Hoehenlage_SW.svg: + category: Sonstiges + name: Höhenlage bei Festsetzungen +# endregion \ No newline at end of file diff --git a/src/SAGisXPlanung/database/create_v2.2.0.sql b/src/SAGisXPlanung/database/create_v2.2.0.sql new file mode 100644 index 0000000..a283087 --- /dev/null +++ b/src/SAGisXPlanung/database/create_v2.2.0.sql @@ -0,0 +1,2617 @@ +BEGIN; + +CREATE TABLE alembic_version ( + version_num VARCHAR(32) NOT NULL, + CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num) +); + +-- Running upgrade -> cfbf4d3b2a9d + +CREATE EXTENSION IF NOT EXISTS postgis;; + +CREATE TYPE xp_bedeutungenbereich AS ENUM ('Teilbereich', 'Kompensationsbereich', 'Sonstiges'); + +CREATE TABLE xp_bereich ( + id UUID NOT NULL, + type VARCHAR(50), + srs VARCHAR(20), + nummer INTEGER NOT NULL, + name VARCHAR, + bedeutung xp_bedeutungenbereich, + "detaillierteBedeutung" VARCHAR, + "erstellungsMassstab" INTEGER, + geltungsbereich geometry(MULTIPOLYGON,-1), + PRIMARY KEY (id) +); + +CREATE INDEX idx_xp_bereich_geltungsbereich ON xp_bereich USING gist (geltungsbereich); + +CREATE TABLE xp_gemeinde ( + id UUID NOT NULL, + ags VARCHAR, + rs VARCHAR, + "gemeindeName" VARCHAR NOT NULL, + "ortsteilName" VARCHAR, + PRIMARY KEY (id) +); + +CREATE TABLE xp_plangeber ( + id UUID NOT NULL, + name VARCHAR NOT NULL, + kennziffer VARCHAR, + PRIMARY KEY (id) +); + +CREATE TYPE xp_rechtsstand AS ENUM ('Geplant', 'Bestehend', 'Fortfallend'); + +CREATE TYPE xp_gesetzliche_grundlage AS ENUM (''); + +CREATE TABLE xp_objekt ( + id UUID NOT NULL, + type VARCHAR(50), + uuid VARCHAR, + text VARCHAR, + rechtsstand xp_rechtsstand, + "gesetzlicheGrundlage" xp_gesetzliche_grundlage, + gliederung1 VARCHAR, + gliederung2 VARCHAR, + ebene INTEGER, + "gehoertZuBereich_id" UUID, + aufschrift VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY("gehoertZuBereich_id") REFERENCES xp_bereich (id) ON DELETE CASCADE +); + +CREATE TABLE xp_plan ( + id UUID NOT NULL, + type VARCHAR(50), + srs VARCHAR(20), + name VARCHAR NOT NULL, + nummer VARCHAR, + "internalId" VARCHAR, + beschreibung VARCHAR, + kommentar VARCHAR, + "technHerstellDatum" DATE, + "genehmigungsDatum" DATE, + "untergangsDatum" DATE, + "erstellungsMassstab" INTEGER, + bezugshoehe FLOAT, + "technischerPlanersteller" VARCHAR, + "raeumlicherGeltungsbereich" geometry(MULTIPOLYGON,-1), + plangeber_id UUID, + PRIMARY KEY (id), + FOREIGN KEY(plangeber_id) REFERENCES xp_plangeber (id) +); + +CREATE INDEX "idx_xp_plan_raeumlicherGeltungsbereich" ON xp_plan USING gist ("raeumlicherGeltungsbereich"); + +CREATE TABLE xp_po ( + id UUID NOT NULL, + "gehoertZuBereich_id" UUID, + type VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY("gehoertZuBereich_id") REFERENCES xp_bereich (id) ON DELETE CASCADE +); + +CREATE TABLE xp_simple_geometry ( + id UUID NOT NULL, + name VARCHAR, + xplanung_type VARCHAR, + position geometry(GEOMETRY,-1), + "gehoertZuBereich_id" UUID, + PRIMARY KEY (id), + FOREIGN KEY("gehoertZuBereich_id") REFERENCES xp_bereich (id) ON DELETE CASCADE +); + +CREATE INDEX idx_xp_simple_geometry_position ON xp_simple_geometry USING gist (position); + +CREATE TYPE bp_rechtscharakter AS ENUM ('Festsetzung', 'NachrichtlicheUebernahme', 'Hinweis', 'Vermerk', 'Kennzeichnung', 'Unbekannt'); + +CREATE TABLE bp_objekt ( + id UUID NOT NULL, + rechtscharakter bp_rechtscharakter, + position geometry(GEOMETRY,-1), + flaechenschluss BOOLEAN, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES xp_objekt (id) ON DELETE CASCADE +); + +CREATE INDEX idx_bp_objekt_position ON bp_objekt USING gist (position); + +CREATE TYPE bp_planart AS ENUM ('BPlan', 'EinfacherBPlan', 'QualifizierterBPlan', 'VorhabenbezogenerBPlan', 'VorhabenUndErschliessungsplan', 'InnenbereichsSatzung', 'KlarstellungsSatzung', 'EntwicklungsSatzung', 'ErgaenzungsSatzung', 'AussenbereichsSatzung', 'OertlicheBauvorschrift', 'Sonstiges'); + +CREATE TYPE bp_verfahren AS ENUM ('Normal', 'Parag13', 'Parag13a', 'Parag13b'); + +CREATE TYPE bp_rechtsstand AS ENUM ('Aufstellungsbeschluss', 'Entwurf', 'FruehzeitigeBehoerdenBeteiligung', 'FruehzeitigeOeffentlichkeitsBeteiligung', 'BehoerdenBeteiligung', 'OeffentlicheAuslegung', 'Satzung', 'InkraftGetreten', 'TeilweiseUntergegangen', 'Untergegangen', 'Aufgehoben', 'AusserKraft'); + +CREATE TYPE xp_verlaengerungveraenderungssperre AS ENUM ('Keine', 'ErsteVerlaengerung', 'ZweiteVerlaengerung'); + +CREATE TABLE bp_plan ( + id UUID NOT NULL, + "planArt" bp_planart NOT NULL, + verfahren bp_verfahren, + rechtsstand bp_rechtsstand, + hoehenbezug VARCHAR, + "aenderungenBisDatum" DATE, + "aufstellungsbeschlussDatum" DATE, + "veraenderungssperreBeschlussDatum" DATE, + "veraenderungssperreDatum" DATE, + "veraenderungssperreEndDatum" DATE, + "verlaengerungVeraenderungssperre" xp_verlaengerungveraenderungssperre, + "auslegungsStartDatum" DATE[], + "auslegungsEndDatum" DATE[], + "traegerbeteiligungsStartDatum" DATE[], + "traegerbeteiligungsEndDatum" DATE[], + "satzungsbeschlussDatum" DATE, + "rechtsverordnungsDatum" DATE, + "inkrafttretensDatum" DATE, + "ausfertigungsDatum" DATE, + veraenderungssperre BOOLEAN, + "staedtebaulicherVertrag" BOOLEAN, + "erschliessungsVertrag" BOOLEAN, + "durchfuehrungsVertrag" BOOLEAN, + gruenordnungsplan BOOLEAN, + "versionBauNVODatum" DATE, + "versionBauNVOText" VARCHAR, + "versionBauGBDatum" DATE, + "versionBauGBText" VARCHAR, + "versionSonstRechtsgrundlageDatum" DATE, + "versionSonstRechtsgrundlageText" VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES xp_plan (id) ON DELETE CASCADE +); + +CREATE TYPE fp_planart AS ENUM ('FPlan', 'GemeinsamerFPlan', 'RegFPlan', 'FPlanRegPlan', 'SachlicherTeilplan', 'Sonstiges'); + +CREATE TYPE fp_verfahren AS ENUM ('Normal', 'Parag13'); + +CREATE TYPE fp_rechtsstand AS ENUM ('Aufstellungsbeschluss', 'Entwurf', 'FruehzeitigeBehoerdenBeteiligung', 'FruehzeitigeOeffentlichkeitsBeteiligung', 'BehoerdenBeteiligung', 'OeffentlicheAuslegung', 'Plan', 'Wirksamkeit', 'Untergegangen', 'Aufgehoben', 'AusserKraft'); + +CREATE TABLE fp_plan ( + id UUID NOT NULL, + "planArt" fp_planart NOT NULL, + sachgebiet VARCHAR, + verfahren fp_verfahren, + rechtsstand fp_rechtsstand, + "aufstellungsbeschlussDatum" DATE, + "auslegungsStartDatum" DATE[], + "auslegungsEndDatum" DATE[], + "traegerbeteiligungsStartDatum" DATE[], + "traegerbeteiligungsEndDatum" DATE[], + "aenderungenBisDatum" DATE, + "entwurfsbeschlussDatum" DATE, + "planbeschlussDatum" DATE, + "wirksamkeitsDatum" DATE, + "versionBauNVODatum" DATE, + "versionBauNVOText" VARCHAR, + "versionBauGBDatum" DATE, + "versionBauGBText" VARCHAR, + "versionSonstRechtsgrundlageDatum" DATE, + "versionSonstRechtsgrundlageText" VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES xp_plan (id) ON DELETE CASCADE +); + +CREATE TABLE xp_plan_gemeinde ( + plan_id UUID, + gemeinde_id UUID, + FOREIGN KEY(gemeinde_id) REFERENCES xp_gemeinde (id), + FOREIGN KEY(plan_id) REFERENCES xp_plan (id) +); + +CREATE TABLE xp_ppo ( + id UUID NOT NULL, + position geometry(POINT,-1), + drehwinkel INTEGER, + skalierung FLOAT, + symbol_path VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES xp_po (id) ON DELETE CASCADE +); + +CREATE INDEX idx_xp_ppo_position ON xp_ppo USING gist (position); + +CREATE TYPE xp_externereferenzart AS ENUM ('Dokument', 'PlanMitGeoreferenz'); + +CREATE TYPE xp_mime_types AS ENUM ('application/pdf', 'application/zip', 'application/xml', 'application/msword', 'application/msexcel', 'application/vnd.ogc.sld+xml', 'application/vnd.ogc.wms_xml', 'application/vnd.ogc.gml', 'application/vnd.shp', 'application/vnd.dbf', 'application/vnd.shx', 'application/octet-stream', 'image/vnd.dxf', 'image/vnd.dwg', 'image/jpg', 'image/png', 'image/tiff', 'image/bmp', 'image/ecw', 'image/svg+xml', 'text/html', 'text/plain'); + +CREATE TYPE xp_externereferenztyp AS ENUM ('Beschreibung', 'Begruendung', 'Legende', 'Rechtsplan', 'Plangrundlage', 'Umweltbericht', 'Satzung', 'Verordnung', 'Karte', 'Erlaeuterung', 'ZusammenfassendeErklaerung', 'Koordinatenliste', 'Grundstuecksverzeichnis', 'Pflanzliste', 'Gruenordnungsplan', 'Erschliessungsvertrag', 'Durchfuehrungsvertrag', 'StaedtebaulicherVertrag', 'UmweltbezogeneStellungnahmen', 'Beschluss', 'VorhabenUndErschliessungsplan', 'MetadatenPlan', 'Genehmigung', 'Bekanntmachung', 'Rechtsverbindlich', 'Informell'); + +CREATE TABLE xp_spez_externe_referenz ( + id UUID NOT NULL, + "georefURL" VARCHAR, + art xp_externereferenzart, + "referenzName" VARCHAR, + "referenzURL" VARCHAR, + "referenzMimeType" xp_mime_types, + beschreibung VARCHAR, + datum DATE, + file BYTEA, + typ xp_externereferenztyp, + plan_id UUID, + PRIMARY KEY (id), + CHECK (NOT("referenzName" IS NULL AND "referenzURL" IS NULL)), + FOREIGN KEY(plan_id) REFERENCES xp_plan (id) ON DELETE CASCADE +); + +CREATE TABLE xp_tpo ( + id UUID NOT NULL, + position geometry(POINT,-1), + drehwinkel INTEGER, + schriftinhalt VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES xp_po (id) ON DELETE CASCADE +); + +CREATE INDEX idx_xp_tpo_position ON xp_tpo USING gist (position); + +CREATE TABLE xp_verfahrens_merkmal ( + id UUID NOT NULL, + plan_id UUID, + vermerk VARCHAR NOT NULL, + datum DATE NOT NULL, + signatur VARCHAR NOT NULL, + signiert BOOLEAN NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY(plan_id) REFERENCES xp_plan (id) ON DELETE CASCADE +); + +CREATE TYPE bp_zulaessigkeit AS ENUM ('Zulaessig', 'NichtZulaessig', 'AusnahmsweiseZulaessig'); + +CREATE TYPE xp_allgartderbaulnutzung AS ENUM ('WohnBauflaeche', 'GemischteBauflaeche', 'GewerblicheBauflaeche', 'SonderBauflaeche', 'Sonstiges'); + +CREATE TYPE xp_besondereartderbaulnutzung AS ENUM ('Kleinsiedlungsgebiet', 'ReinesWohngebiet', 'AllgWohngebiet', 'BesonderesWohngebiet', 'Dorfgebiet', 'Mischgebiet', 'UrbanesGebiet', 'Kerngebiet', 'Gewerbegebiet', 'Industriegebiet', 'SondergebietErholung', 'SondergebietSonst', 'Wochenendhausgebiet', 'Sondergebiet', 'SonstigesGebiet'); + +CREATE TYPE xp_sondernutzungen AS ENUM ('KeineSondernutzung', 'Wochendhausgebiet', 'Ferienhausgebiet', 'Campingplatzgebiet', 'Kurgebiet', 'SonstSondergebietErholung', 'Einzelhandelsgebiet', 'GrossflaechigerEinzelhandel', 'Ladengebiet', 'Einkaufszentrum', 'SonstGrossflEinzelhandel', 'Verkehrsuebungsplatz', 'Hafengebiet', 'SondergebietErneuerbareEnergie', 'SondergebietMilitaer', 'SondergebietLandwirtschaft', 'SondergebietSport', 'SondergebietGesundheitSoziales', 'Klinikgebiet', 'Golfplatz', 'SondergebietKultur', 'SondergebietTourismus', 'SondergebietBueroUndVerwaltung', 'SondergebietJustiz', 'SondergebietHochschuleForschung', 'SondergebietMesse', 'SondergebietAndereNutzungen'); + +CREATE TYPE xp_abweichungbaunvotypen AS ENUM ('KeineAbweichung', 'EinschraenkungNutzung', 'AusschlussNutzung', 'AusweitungNutzung', 'SonstAbweichung'); + +CREATE TYPE bp_bauweise AS ENUM ('KeineAngabe', 'OffeneBauweise', 'GeschlosseneBauweise', 'AbweichendeBauweise'); + +CREATE TYPE bp_bebauungsart AS ENUM ('Einzelhaeuser', 'Doppelhaeuser', 'Hausgruppen', 'EinzelDoppelhaeuser', 'EinzelhaeuserHausgruppen', 'DoppelhaeuserHausgruppen', 'Reihenhaeuser', 'EinzelhaeuserDoppelhaeuserHausgruppen'); + +CREATE TYPE bp_grenzbebauung AS ENUM ('KeineAngabe', 'Verboten', 'Erlaubt', 'Erzwungen'); + +CREATE TABLE bp_baugebiet ( + id UUID NOT NULL, + "FR" INTEGER, + "MaxZahlWohnungen" INTEGER, + "MinGRWohneinheit" FLOAT, + "Fmin" FLOAT, + "Fmax" FLOAT, + "Bmin" FLOAT, + "Bmax" FLOAT, + "Tmin" FLOAT, + "Tmax" FLOAT, + "GFZmin" FLOAT, + "GFZmax" FLOAT, + "GFZ" FLOAT, + "GFZ_Ausn" FLOAT, + "GFmin" FLOAT, + "GFmax" FLOAT, + "GF" FLOAT, + "GF_Ausn" FLOAT, + "BMZ" FLOAT, + "BMZ_Ausn" FLOAT, + "BM" FLOAT, + "BM_Ausn" FLOAT, + "GRZmin" FLOAT, + "GRZmax" FLOAT, + "GRZ" FLOAT, + "GRZ_Ausn" FLOAT, + "GRmin" FLOAT, + "GRmax" FLOAT, + "GR" FLOAT, + "GR_Ausn" FLOAT, + "Zmin" INTEGER, + "Zmax" INTEGER, + "Zzwingend" INTEGER, + "Z" INTEGER, + "Z_Ausn" INTEGER, + "Z_Staffel" INTEGER, + "Z_Dach" INTEGER, + "ZUmin" INTEGER, + "ZUmax" INTEGER, + "ZUzwingend" INTEGER, + "ZU" INTEGER, + "ZU_Ausn" INTEGER, + "wohnnutzungEGStrasse" bp_zulaessigkeit, + "ZWohn" INTEGER, + "GFAntWohnen" INTEGER, + "GFWohnen" FLOAT, + "GFAntGewerbe" INTEGER, + "GFGewerbe" FLOAT, + "VF" FLOAT, + "allgArtDerBaulNutzung" xp_allgartderbaulnutzung, + "besondereArtDerBaulNutzung" xp_besondereartderbaulnutzung, + sondernutzung xp_sondernutzungen, + "nutzungText" VARCHAR, + "abweichungBauNVO" xp_abweichungbaunvotypen, + bauweise bp_bauweise, + "vertikaleDifferenzierung" BOOLEAN, + "bebauungsArt" bp_bebauungsart, + "bebauungVordereGrenze" bp_grenzbebauung, + "bebauungRueckwaertigeGrenze" bp_grenzbebauung, + "bebauungSeitlicheGrenze" bp_grenzbebauung, + "zugunstenVon" VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TABLE bp_baugrenze ( + id UUID NOT NULL, + bautiefe FLOAT, + "geschossMin" INTEGER, + "geschossMax" INTEGER, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TABLE bp_bereich ( + id UUID NOT NULL, + "versionBauGBDatum" DATE, + "versionBauGBText" VARCHAR, + "versionSonstRechtsgrundlageDatum" DATE, + "versionSonstRechtsgrundlageText" VARCHAR, + "gehoertZuPlan_id" UUID, + PRIMARY KEY (id), + FOREIGN KEY("gehoertZuPlan_id") REFERENCES bp_plan (id) ON DELETE CASCADE, + FOREIGN KEY(id) REFERENCES xp_bereich (id) ON DELETE CASCADE +); + +CREATE TABLE fp_bereich ( + id UUID NOT NULL, + "gehoertZuPlan_id" UUID, + PRIMARY KEY (id), + FOREIGN KEY("gehoertZuPlan_id") REFERENCES fp_plan (id) ON DELETE CASCADE, + FOREIGN KEY(id) REFERENCES xp_bereich (id) ON DELETE CASCADE +); + +CREATE TYPE bp_dachform AS ENUM ('Flachdach', 'Pultdach', 'VersetztesPultdach', 'GeneigtesDach', 'Satteldach', 'Walmdach', 'KrueppelWalmdach', 'Mansarddach', 'Zeltdach', 'Kegeldach', 'Kuppeldach', 'Sheddach', 'Bogendach', 'Turmdach', 'Tonnendach', 'Mischform', 'Sonstiges'); + +CREATE TABLE bp_dachgestaltung ( + id UUID NOT NULL, + "DNmin" INTEGER, + "DNmax" INTEGER, + "DN" INTEGER, + "DNZwingend" INTEGER, + dachform bp_dachform, + baugebiet_id UUID, + PRIMARY KEY (id), + FOREIGN KEY(baugebiet_id) REFERENCES bp_baugebiet (id) ON DELETE CASCADE +); + +CREATE TABLE xp_externe_referenz ( + id UUID NOT NULL, + "georefURL" VARCHAR, + art xp_externereferenzart, + "referenzName" VARCHAR, + "referenzURL" VARCHAR, + "referenzMimeType" xp_mime_types, + beschreibung VARCHAR, + datum DATE, + file BYTEA, + bereich_id UUID, + baugebiet_id UUID, + PRIMARY KEY (id), + CHECK (NOT("referenzName" IS NULL AND "referenzURL" IS NULL)), + FOREIGN KEY(baugebiet_id) REFERENCES bp_baugebiet (id) ON DELETE CASCADE, + FOREIGN KEY(bereich_id) REFERENCES xp_bereich (id) ON DELETE CASCADE +); + +INSERT INTO alembic_version (version_num) VALUES ('cfbf4d3b2a9d') RETURNING alembic_version.version_num; + +-- Running upgrade cfbf4d3b2a9d -> 20c50b38b0af + +CREATE TABLE bp_flaeche_ohne_festsetzung ( + id UUID NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +ALTER TABLE bp_objekt ALTER COLUMN rechtscharakter SET NOT NULL; + +ALTER TABLE xp_bereich DROP COLUMN srs; + +ALTER TABLE xp_plan DROP COLUMN srs; + +UPDATE alembic_version SET version_num='20c50b38b0af' WHERE alembic_version.version_num = 'cfbf4d3b2a9d'; + +-- Running upgrade 20c50b38b0af -> 2cee32cfc646 + +CREATE TABLE bp_baulinie ( + id UUID NOT NULL, + bautiefe FLOAT, + "geschossMin" INTEGER, + "geschossMax" INTEGER, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TABLE bp_besondere_nutzung ( + id UUID NOT NULL, + "FR" FLOAT, + "MaxZahlWohnungen" INTEGER, + "MinGRWohneinheit" FLOAT, + "Fmin" FLOAT, + "Fmax" FLOAT, + "Bmin" FLOAT, + "Bmax" FLOAT, + "Tmin" FLOAT, + "Tmax" FLOAT, + "GFZmin" FLOAT, + "GFZmax" FLOAT, + "GFZ" FLOAT, + "GFZ_Ausn" FLOAT, + "GFmin" FLOAT, + "GFmax" FLOAT, + "GF" FLOAT, + "GF_Ausn" FLOAT, + "BMZ" FLOAT, + "BMZ_Ausn" FLOAT, + "BM" FLOAT, + "BM_Ausn" FLOAT, + "GRZmin" FLOAT, + "GRZmax" FLOAT, + "GRZ" FLOAT, + "GRZ_Ausn" FLOAT, + "GRmin" FLOAT, + "GRmax" FLOAT, + "GR" FLOAT, + "GR_Ausn" FLOAT, + "Zmin" INTEGER, + "Zmax" INTEGER, + "Zzwingend" INTEGER, + "Z" INTEGER, + "Z_Ausn" INTEGER, + "Z_Staffel" INTEGER, + "Z_Dach" INTEGER, + "ZUmin" INTEGER, + "ZUmax" INTEGER, + "ZUzwingend" INTEGER, + "ZU" INTEGER, + "ZU_Ausn" INTEGER, + zweckbestimmung VARCHAR, + bauweise bp_bauweise, + "bebauungsArt" bp_bebauungsart, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE xp_zweckbestimmunggemeinbedarf AS ENUM ('OeffentlicheVerwaltung', 'KommunaleEinrichtung', 'BetriebOeffentlZweckbestimmung', 'AnlageBundLand', 'BildungForschung', 'Schule', 'Hochschule', 'BerufsbildendeSchule', 'Forschungseinrichtung', 'Kirche', 'Sakralgebaeude', 'KirchlicheVerwaltung', 'Kirchengemeinde', 'Sozial', 'EinrichtungKinder', 'EinrichtungJugendliche', 'EinrichtungFamilienErwachsene', 'EinrichtungSenioren', 'SonstigeSozialeEinrichtung', 'EinrichtungBehinderte', 'Gesundheit', 'Krankenhaus', 'Kultur', 'MusikTheater', 'Bildung', 'Sport', 'Bad', 'SportplatzSporthalle', 'SicherheitOrdnung', 'Feuerwehr', 'Schutzbauwerk', 'Justiz', 'Infrastruktur', 'Post', 'Sonstiges'); + +CREATE TABLE bp_gemeinbedarf ( + id UUID NOT NULL, + "FR" INTEGER, + "MaxZahlWohnungen" INTEGER, + "MinGRWohneinheit" FLOAT, + "Fmin" FLOAT, + "Fmax" FLOAT, + "Bmin" FLOAT, + "Bmax" FLOAT, + "Tmin" FLOAT, + "Tmax" FLOAT, + "GFZmin" FLOAT, + "GFZmax" FLOAT, + "GFZ" FLOAT, + "GFZ_Ausn" FLOAT, + "GFmin" FLOAT, + "GFmax" FLOAT, + "GF" FLOAT, + "GF_Ausn" FLOAT, + "BMZ" FLOAT, + "BMZ_Ausn" FLOAT, + "BM" FLOAT, + "BM_Ausn" FLOAT, + "GRZmin" FLOAT, + "GRZmax" FLOAT, + "GRZ" FLOAT, + "GRZ_Ausn" FLOAT, + "GRmin" FLOAT, + "GRmax" FLOAT, + "GR" FLOAT, + "GR_Ausn" FLOAT, + "Zmin" INTEGER, + "Zmax" INTEGER, + "Zzwingend" INTEGER, + "Z" INTEGER, + "Z_Ausn" INTEGER, + "Z_Staffel" INTEGER, + "Z_Dach" INTEGER, + "ZUmin" INTEGER, + "ZUmax" INTEGER, + "ZUzwingend" INTEGER, + "ZU" INTEGER, + "ZU_Ausn" INTEGER, + zweckbestimmung xp_zweckbestimmunggemeinbedarf, + bauweise bp_bauweise, + "bebauungsArt" bp_bebauungsart, + "zugunstenVon" VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE xp_zweckbestimmunggewaesser AS ENUM ('Hafen', 'Sportboothafen', 'Wasserflaeche', 'Fliessgewaesser', 'Sonstiges'); + +CREATE TABLE bp_gewaesser ( + id UUID NOT NULL, + zweckbestimmung xp_zweckbestimmunggewaesser, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE xp_zweckbestimmunggruen AS ENUM ('Parkanlage', 'ParkanlageHistorisch', 'ParkanlageNaturnah', 'ParkanlageWaldcharakter', 'NaturnaheUferParkanlage', 'Dauerkleingarten', 'ErholungsGaerten', 'Sportplatz', 'Reitsportanlage', 'Hundesportanlage', 'Wassersportanlage', 'Schiessstand', 'Golfplatz', 'Skisport', 'Tennisanlage', 'Spielplatz', 'Bolzplatz', 'Abenteuerspielplatz', 'Zeltplatz', 'Campingplatz', 'Badeplatz', 'FreizeitErholung', 'Kleintierhaltung', 'Festplatz', 'SpezGruenflaeche', 'StrassenbegleitGruen', 'BoeschungsFlaeche', 'FeldWaldWiese', 'Uferschutzstreifen', 'Abschirmgruen', 'UmweltbildungsparkSchaugatter', 'RuhenderVerkehr', 'Friedhof', 'Sonstiges', 'Gaertnerei'); + +CREATE TYPE xp_nutzungsform AS ENUM ('Privat', 'Oeffentlich'); + +CREATE TABLE bp_gruenflaeche ( + id UUID NOT NULL, + "MaxZahlWohnungen" INTEGER, + "MinGRWohneinheit" FLOAT, + "Fmin" FLOAT, + "Fmax" FLOAT, + "Bmin" FLOAT, + "Bmax" FLOAT, + "Tmin" FLOAT, + "Tmax" FLOAT, + "GFZmin" FLOAT, + "GFZmax" FLOAT, + "GFZ" FLOAT, + "GFZ_Ausn" FLOAT, + "GFmin" FLOAT, + "GFmax" FLOAT, + "GF" FLOAT, + "GF_Ausn" FLOAT, + "BMZ" FLOAT, + "BMZ_Ausn" FLOAT, + "BM" FLOAT, + "BM_Ausn" FLOAT, + "GRZmin" FLOAT, + "GRZmax" FLOAT, + "GRZ" FLOAT, + "GRZ_Ausn" FLOAT, + "GRmin" FLOAT, + "GRmax" FLOAT, + "GR" FLOAT, + "GR_Ausn" FLOAT, + "Zmin" INTEGER, + "Zmax" INTEGER, + "Zzwingend" INTEGER, + "Z" INTEGER, + "Z_Ausn" INTEGER, + "Z_Staffel" INTEGER, + "Z_Dach" INTEGER, + "ZUmin" INTEGER, + "ZUmax" INTEGER, + "ZUzwingend" INTEGER, + "ZU" INTEGER, + "ZU_Ausn" INTEGER, + zweckbestimmung xp_zweckbestimmunggruen, + nutzungsform xp_nutzungsform, + "zugunstenVon" VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE xp_zweckbestimmunglandwirtschaft AS ENUM ('LandwirtschaftAllgemein', 'Ackerbau', 'WiesenWeidewirtschaft', 'GartenbaulicheErzeugung', 'Obstbau', 'Weinbau', 'Imkerei', 'Binnenfischerei', 'Sonstiges'); + +CREATE TABLE bp_landwirtschaft ( + id UUID NOT NULL, + zweckbestimmung xp_zweckbestimmunglandwirtschaft, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE xp_abemassnahmentypen AS ENUM ('BindungErhaltung', 'Anpflanzung'); + +CREATE TYPE xp_anpflanzungbindungerhaltungsgegenstand AS ENUM ('Baeume', 'Kopfbaeume', 'Baumreihe', 'Straeucher', 'BaeumeUndStraeucher', 'Hecke', 'Knick', 'SonstBepflanzung', 'Gewaesser', 'Fassadenbegruenung', 'Dachbegruenung'); + +CREATE TABLE bp_pflanzung ( + id UUID NOT NULL, + massnahme xp_abemassnahmentypen, + gegenstand xp_anpflanzungbindungerhaltungsgegenstand, + kronendurchmesser FLOAT, + pflanztiefe FLOAT, + "istAusgleich" BOOLEAN, + "baumArt" VARCHAR, + mindesthoehe FLOAT, + anzahl INTEGER, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE xp_zweckbestimmungspielsportanlage AS ENUM ('Sportanlage', 'Spielanlage', 'SpielSportanlage', 'Sonstiges'); + +CREATE TABLE bp_spiel_sportanlage ( + id UUID NOT NULL, + "MaxZahlWohnungen" INTEGER, + "MinGRWohneinheit" FLOAT, + "Fmin" FLOAT, + "Fmax" FLOAT, + "Bmin" FLOAT, + "Bmax" FLOAT, + "Tmin" FLOAT, + "Tmax" FLOAT, + "GFZmin" FLOAT, + "GFZmax" FLOAT, + "GFZ" FLOAT, + "GFZ_Ausn" FLOAT, + "GFmin" FLOAT, + "GFmax" FLOAT, + "GF" FLOAT, + "GF_Ausn" FLOAT, + "BMZ" FLOAT, + "BMZ_Ausn" FLOAT, + "BM" FLOAT, + "BM_Ausn" FLOAT, + "GRZmin" FLOAT, + "GRZmax" FLOAT, + "GRZ" FLOAT, + "GRZ_Ausn" FLOAT, + "GRmin" FLOAT, + "GRmax" FLOAT, + "GR" FLOAT, + "GR_Ausn" FLOAT, + "Zmin" INTEGER, + "Zmax" INTEGER, + "Zzwingend" INTEGER, + "Z" INTEGER, + "Z_Ausn" INTEGER, + "Z_Staffel" INTEGER, + "Z_Dach" INTEGER, + "ZUmin" INTEGER, + "ZUmax" INTEGER, + "ZUzwingend" INTEGER, + "ZU" INTEGER, + "ZU_Ausn" INTEGER, + zweckbestimmung xp_zweckbestimmungspielsportanlage, + "zugunstenVon" VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TABLE bp_strassenbegrenzung ( + id UUID NOT NULL, + bautiefe FLOAT, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TABLE bp_strassenverkehr ( + id UUID NOT NULL, + "MaxZahlWohnungen" INTEGER, + "MinGRWohneinheit" FLOAT, + "Fmin" FLOAT, + "Fmax" FLOAT, + "Bmin" FLOAT, + "Bmax" FLOAT, + "Tmin" FLOAT, + "Tmax" FLOAT, + "GFZmin" FLOAT, + "GFZmax" FLOAT, + "GFZ" FLOAT, + "GFZ_Ausn" FLOAT, + "GFmin" FLOAT, + "GFmax" FLOAT, + "GF" FLOAT, + "GF_Ausn" FLOAT, + "BMZ" FLOAT, + "BMZ_Ausn" FLOAT, + "BM" FLOAT, + "BM_Ausn" FLOAT, + "GRZmin" FLOAT, + "GRZmax" FLOAT, + "GRZ" FLOAT, + "GRZ_Ausn" FLOAT, + "GRmin" FLOAT, + "GRmax" FLOAT, + "GR" FLOAT, + "GR_Ausn" FLOAT, + "Zmin" INTEGER, + "Zmax" INTEGER, + "Zzwingend" INTEGER, + "Z" INTEGER, + "Z_Ausn" INTEGER, + "Z_Staffel" INTEGER, + "Z_Dach" INTEGER, + "ZUmin" INTEGER, + "ZUmax" INTEGER, + "ZUzwingend" INTEGER, + "ZU" INTEGER, + "ZU_Ausn" INTEGER, + nutzungsform xp_nutzungsform, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE bp_zweckbestimmungstrassenverkehr AS ENUM ('Parkierungsflaeche', 'Fussgaengerbereich', 'VerkehrsberuhigterBereich', 'RadGehweg', 'Radweg', 'Gehweg', 'Wanderweg', 'ReitKutschweg', 'Wirtschaftsweg', 'FahrradAbstellplatz', 'UeberfuehrenderVerkehrsweg', 'UnterfuehrenderVerkehrsweg', 'P_RAnlage', 'Platz', 'Anschlussflaeche', 'LandwirtschaftlicherVerkehr', 'Verkehrsgruen', 'Rastanlage', 'Busbahnhof', 'CarSharing', 'BikeSharing', 'B_RAnlage', 'Parkhaus', 'Mischverkehrsflaeche', 'Ladestation', 'Sonstiges'); + +CREATE TABLE bp_verkehr_besonders ( + id UUID NOT NULL, + "MaxZahlWohnungen" INTEGER, + "MinGRWohneinheit" FLOAT, + "Fmin" FLOAT, + "Fmax" FLOAT, + "Bmin" FLOAT, + "Bmax" FLOAT, + "Tmin" FLOAT, + "Tmax" FLOAT, + "GFZmin" FLOAT, + "GFZmax" FLOAT, + "GFZ" FLOAT, + "GFZ_Ausn" FLOAT, + "GFmin" FLOAT, + "GFmax" FLOAT, + "GF" FLOAT, + "GF_Ausn" FLOAT, + "BMZ" FLOAT, + "BMZ_Ausn" FLOAT, + "BM" FLOAT, + "BM_Ausn" FLOAT, + "GRZmin" FLOAT, + "GRZmax" FLOAT, + "GRZ" FLOAT, + "GRZ_Ausn" FLOAT, + "GRmin" FLOAT, + "GRmax" FLOAT, + "GR" FLOAT, + "GR_Ausn" FLOAT, + "Zmin" INTEGER, + "Zmax" INTEGER, + "Zzwingend" INTEGER, + "Z" INTEGER, + "Z_Ausn" INTEGER, + "Z_Staffel" INTEGER, + "Z_Dach" INTEGER, + "ZUmin" INTEGER, + "ZUmax" INTEGER, + "ZUzwingend" INTEGER, + "ZU" INTEGER, + "ZU_Ausn" INTEGER, + zweckbestimmung bp_zweckbestimmungstrassenverkehr, + nutzungsform xp_nutzungsform, + "zugunstenVon" VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE xp_zweckbestimmungverentsorgung AS ENUM ('Elektrizitaet', 'Hochspannungsleitung', 'TrafostationUmspannwerk', 'Solarkraftwerk', 'Windkraftwerk', 'Geothermiekraftwerk', 'Elektrizitaetswerk', 'Wasserkraftwerk', 'BiomasseKraftwerk', 'Kabelleitung', 'Niederspannungsleitung', 'Leitungsmast', 'Kernkraftwerk', 'Kohlekraftwerk', 'Gaskraftwerk', 'Gas', 'Ferngasleitung', 'Gaswerk', 'Gasbehaelter', 'Gasdruckregler', 'Gasstation', 'Gasleitung', 'Erdoel', 'Erdoelleitung', 'Bohrstelle', 'Erdoelpumpstation', 'Oeltank', 'Waermeversorgung', 'Blockheizkraftwerk', 'Fernwaermeleitung', 'Fernheizwerk', 'Wasser', 'Wasserwerk', 'Wasserleitung', 'Wasserspeicher', 'Brunnen', 'Pumpwerk', 'Quelle', 'Abwasser', 'Abwasserleitung', 'Abwasserrueckhaltebecken', 'Abwasserpumpwerk', 'Klaeranlage', 'AnlageKlaerschlamm', 'SonstigeAbwasserBehandlungsanlage', 'SalzOderSoleleitungen', 'Regenwasser', 'RegenwasserRueckhaltebecken', 'Niederschlagswasserleitung', 'Abfallentsorgung', 'Muellumladestation', 'Muellbeseitigungsanlage', 'Muellsortieranlage', 'Recyclinghof', 'Ablagerung', 'Erdaushubdeponie', 'Bauschuttdeponie', 'Hausmuelldeponie', 'Sondermuelldeponie', 'StillgelegteDeponie', 'RekultivierteDeponie', 'Telekommunikation', 'Fernmeldeanlage', 'Mobilfunkanlage', 'Fernmeldekabel', 'ErneuerbareEnergien', 'KraftWaermeKopplung', 'Sonstiges', 'Produktenleitung'); + +CREATE TABLE bp_versorgung ( + id UUID NOT NULL, + "MaxZahlWohnungen" INTEGER, + "MinGRWohneinheit" FLOAT, + "Fmin" FLOAT, + "Fmax" FLOAT, + "Bmin" FLOAT, + "Bmax" FLOAT, + "Tmin" FLOAT, + "Tmax" FLOAT, + "GFZmin" FLOAT, + "GFZmax" FLOAT, + "GFZ" FLOAT, + "GFZ_Ausn" FLOAT, + "GFmin" FLOAT, + "GFmax" FLOAT, + "GF" FLOAT, + "GF_Ausn" FLOAT, + "BMZ" FLOAT, + "BMZ_Ausn" FLOAT, + "BM" FLOAT, + "BM_Ausn" FLOAT, + "GRZmin" FLOAT, + "GRZmax" FLOAT, + "GRZ" FLOAT, + "GRZ_Ausn" FLOAT, + "GRmin" FLOAT, + "GRmax" FLOAT, + "GR" FLOAT, + "GR_Ausn" FLOAT, + "Zmin" INTEGER, + "Zmax" INTEGER, + "Zzwingend" INTEGER, + "Z" INTEGER, + "Z_Ausn" INTEGER, + "Z_Staffel" INTEGER, + "Z_Dach" INTEGER, + "ZUmin" INTEGER, + "ZUmax" INTEGER, + "ZUzwingend" INTEGER, + "ZU" INTEGER, + "ZU_Ausn" INTEGER, + zweckbestimmung xp_zweckbestimmungverentsorgung, + "textlicheErgaenzung" VARCHAR, + "zugunstenVon" VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE xp_zweckbestimmungwald AS ENUM ('Naturwald', 'Waldschutzgebiet', 'Nutzwald', 'Erholungswald', 'Schutzwald', 'Bodenschutzwald', 'Biotopschutzwald', 'NaturnaherWald', 'SchutzwaldSchaedlicheUmwelteinwirkungen', 'Schonwald', 'Bannwald', 'FlaecheForstwirtschaft', 'ImmissionsgeschaedigterWald', 'Sonstiges'); + +CREATE TYPE xp_eigentumsartwald AS ENUM ('OeffentlicherWald', 'Staatswald', 'Koerperschaftswald', 'Kommunalwald', 'Stiftungswald', 'Privatwald', 'Gemeinschaftswald', 'Genossenschaftswald', 'Kirchenwald', 'Sonstiges'); + +CREATE TYPE xp_waldbetretungtyp AS ENUM ('KeineZusaetzlicheBetretung', 'Radfahren', 'Reiten', 'Fahren', 'Hundesport', 'Sonstiges'); + +CREATE TABLE bp_wald ( + id UUID NOT NULL, + zweckbestimmung xp_zweckbestimmungwald, + eigentumsart xp_eigentumsartwald, + betreten xp_waldbetretungtyp, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +ALTER TABLE bp_dachgestaltung ADD COLUMN besondere_nutzung_id UUID; + +ALTER TABLE bp_dachgestaltung ADD COLUMN gemeinbedarf_id UUID; + +ALTER TABLE bp_dachgestaltung ADD CONSTRAINT fk_dachgestaltung_gemeinbedarf FOREIGN KEY(gemeinbedarf_id) REFERENCES bp_gemeinbedarf (id) ON DELETE CASCADE; + +ALTER TABLE bp_dachgestaltung ADD CONSTRAINT fk_dachgestaltung_besondere_nutzung FOREIGN KEY(besondere_nutzung_id) REFERENCES bp_besondere_nutzung (id) ON DELETE CASCADE; + +UPDATE alembic_version SET version_num='2cee32cfc646' WHERE alembic_version.version_num = '20c50b38b0af'; + +-- Running upgrade 2cee32cfc646 -> 54455ce1e9f6 + +CREATE TYPE xp_speziele AS ENUM ('SchutzPflege', 'Entwicklung', 'Anlage', 'SchutzPflegeEntwicklung', 'Sonstiges'); + +CREATE TABLE bp_schutzflaeche ( + id UUID NOT NULL, + ziel xp_speziele, + "sonstZiel" VARCHAR, + "istAusgleich" BOOLEAN, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE xp_spemassnahmentypen AS ENUM ('ArtentreicherGehoelzbestand', 'NaturnaherWald', 'ExtensivesGruenland', 'Feuchtgruenland', 'Obstwiese', 'NaturnaherUferbereich', 'Roehrichtzone', 'Ackerrandstreifen', 'Ackerbrache', 'Gruenlandbrache', 'Sukzessionsflaeche', 'Hochstaudenflur', 'Trockenrasen', 'Heide', 'Sonstiges'); + +CREATE TABLE xp_spe_daten ( + id UUID NOT NULL, + "klassifizMassnahme" xp_spemassnahmentypen, + "massnahmeText" VARCHAR, + "massnahmeKuerzel" VARCHAR, + bp_schutzflaeche_id UUID, + PRIMARY KEY (id), + FOREIGN KEY(bp_schutzflaeche_id) REFERENCES bp_schutzflaeche (id) ON DELETE CASCADE +); + +CREATE TYPE bp_wegerechttypen AS ENUM ('Gehrecht', 'Fahrrecht', 'Radfahrrecht', 'Leitungsrecht', 'Sonstiges'); + +CREATE TABLE bp_wegerecht ( + id UUID NOT NULL, + typ bp_wegerechttypen[], + "zugunstenVon" VARCHAR, + thema VARCHAR, + breite FLOAT, + "istSchmal" BOOLEAN, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES bp_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE xp_bundeslaender AS ENUM ('BB', 'BE', 'BW', 'BY', 'HB', 'HE', 'HH', 'MV', 'NI', 'NW', 'RP', 'SH', 'SL', 'SN', 'ST', 'TH', 'Bund'); + +CREATE TYPE rp_art AS ENUM ('Regionalplan', 'SachlicherTeilplanRegionalebene', 'SachlicherTeilplanLandesebene', 'Braunkohlenplan', 'LandesweiterRaumordnungsplan', 'StandortkonzeptBund', 'AWZPlan', 'RaeumlicherTeilplan', 'Sonstiges'); + +CREATE TYPE rp_rechtsstand AS ENUM ('Aufstellungsbeschluss', 'Entwurf', 'EntwurfGenehmigt', 'EntwurfGeaendert', 'EntwurfAufgegeben', 'EntwurfRuht', 'Plan', 'Inkraftgetreten', 'AllgemeinePlanungsabsicht', 'AusserKraft', 'PlanUngueltig'); + +CREATE TYPE rp_verfahren AS ENUM ('Aenderung', 'Teilfortschreibung', 'Neuaufstellung', 'Gesamtfortschreibung', 'Aktualisierung', 'Neubekanntmachung'); + +CREATE TABLE rp_plan ( + id UUID NOT NULL, + bundesland xp_bundeslaender, + "planArt" rp_art NOT NULL, + planungsregion INTEGER, + teilabschnitt INTEGER, + rechtsstand rp_rechtsstand, + "aufstellungsbeschlussDatum" DATE, + "auslegungsStartDatum" DATE[], + "auslegungsEndDatum" DATE[], + "traegerbeteiligungsStartDatum" DATE[], + "traegerbeteiligungsEndDatum" DATE[], + "aenderungenBisDatum" DATE, + "entwurfsbeschlussDatum" DATE, + "planbeschlussDatum" DATE, + "datumDesInkrafttretens" DATE, + verfahren rp_verfahren, + "amtlicherSchluessel" VARCHAR, + genehmigungsbehoerde VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES xp_plan (id) ON DELETE CASCADE +); + +CREATE TABLE rp_bereich ( + id UUID NOT NULL, + "versionBROG" DATE, + "versionBROGText" VARCHAR, + "versionLPLG" DATE, + "versionLPLGText" VARCHAR, + geltungsmassstab INTEGER, + "gehoertZuPlan_id" UUID, + PRIMARY KEY (id), + FOREIGN KEY("gehoertZuPlan_id") REFERENCES rp_plan (id) ON DELETE CASCADE, + FOREIGN KEY(id) REFERENCES xp_bereich (id) ON DELETE CASCADE +); + +CREATE TYPE lp_planart AS ENUM ('Landschaftsprogramm', 'Landschaftsrahmenplan', 'Landschaftsplan', 'Gruenordnungsplan', 'Sonstiges'); + +CREATE TYPE lp_rechtsstand AS ENUM ('Aufstellungsbeschluss', 'Entwurf', 'Plan', 'Wirksamkeit', 'Untergegangen'); + +CREATE TABLE lp_plan ( + id UUID NOT NULL, + bundesland xp_bundeslaender, + "rechtlicheAussenwirkung" BOOLEAN, + "planArt" lp_planart[] NOT NULL, + "planungstraegerGKZ" VARCHAR, + planungstraeger VARCHAR, + rechtsstand lp_rechtsstand, + "aufstellungsbeschlussDatum" DATE, + "auslegungsDatum" DATE[], + "tOeBbeteiligungsDatum" DATE[], + "oeffentlichkeitsbeteiligungDatum" DATE[], + "aenderungenBisDatum" DATE, + "entwurfsbeschlussDatum" DATE, + "planbeschlussDatum" DATE, + "inkrafttretenDatum" DATE, + "sonstVerfahrensDatum" DATE, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES xp_plan (id) ON DELETE CASCADE +); + +CREATE TABLE lp_bereich ( + id UUID NOT NULL, + "gehoertZuPlan_id" UUID, + PRIMARY KEY (id), + FOREIGN KEY("gehoertZuPlan_id") REFERENCES lp_plan (id) ON DELETE CASCADE, + FOREIGN KEY(id) REFERENCES xp_bereich (id) ON DELETE CASCADE +); + +ALTER TABLE xp_externe_referenz ADD COLUMN bp_schutzflaeche_massnahme_id UUID; + +ALTER TABLE xp_externe_referenz ADD COLUMN bp_schutzflaeche_plan_id UUID; + +ALTER TABLE xp_externe_referenz ADD CONSTRAINT fk_schutzflaeche_plan_ref FOREIGN KEY(bp_schutzflaeche_plan_id) REFERENCES bp_schutzflaeche (id) ON DELETE CASCADE; + +ALTER TABLE xp_externe_referenz ADD CONSTRAINT fk_schutzflaeche_massnahme_ref FOREIGN KEY(bp_schutzflaeche_massnahme_id) REFERENCES bp_schutzflaeche (id) ON DELETE CASCADE; + +ALTER TABLE xp_tpo ADD COLUMN skalierung FLOAT DEFAULT '0.5'; + +ALTER TABLE xp_objekt ADD COLUMN skalierung FLOAT DEFAULT '0.5'; + +ALTER TABLE xp_objekt ADD COLUMN drehwinkel INTEGER DEFAULT '0'; + +ALTER TABLE bp_plan ADD COLUMN plangeber_id UUID; + +ALTER TABLE bp_plan ADD CONSTRAINT fk_plangeber_bp_plan FOREIGN KEY(plangeber_id) REFERENCES xp_plangeber (id); + +ALTER TABLE fp_plan ADD COLUMN plangeber_id UUID; + +ALTER TABLE fp_plan ADD CONSTRAINT fk_plangeber_fp_plan FOREIGN KEY(plangeber_id) REFERENCES xp_plangeber (id); + +UPDATE bp_plan bp SET plangeber_id = xp.plangeber_id FROM xp_plan xp WHERE xp.id = bp.id;; + +UPDATE fp_plan fp SET plangeber_id = xp.plangeber_id FROM xp_plan xp WHERE xp.id = fp.id;; + +ALTER TABLE xp_plan DROP CONSTRAINT xp_plan_plangeber_id_fkey; + +ALTER TABLE xp_plan DROP COLUMN plangeber_id; + +ALTER TABLE xp_plan_gemeinde ADD COLUMN bp_plan_id UUID; + +ALTER TABLE xp_plan_gemeinde ADD COLUMN fp_plan_id UUID; + +ALTER TABLE xp_plan_gemeinde DROP CONSTRAINT xp_plan_gemeinde_plan_id_fkey; + +ALTER TABLE xp_plan_gemeinde ADD CONSTRAINT xp_plan_gemeinde_bp_plan_id_fkey FOREIGN KEY(bp_plan_id) REFERENCES bp_plan (id) ON DELETE CASCADE; + +ALTER TABLE xp_plan_gemeinde ADD CONSTRAINT xp_plan_gemeinde_fp_plan_id_fkey FOREIGN KEY(fp_plan_id) REFERENCES fp_plan (id) ON DELETE CASCADE; + +UPDATE xp_plan_gemeinde g SET bp_plan_id = xp.id FROM xp_plan xp WHERE xp.id = g.plan_id AND xp.type = 'bp_plan';; + +UPDATE xp_plan_gemeinde g SET fp_plan_id = xp.id FROM xp_plan xp WHERE xp.id = g.plan_id AND xp.type = 'fp_plan';; + +ALTER TABLE xp_plan_gemeinde DROP COLUMN plan_id; + +UPDATE alembic_version SET version_num='54455ce1e9f6' WHERE alembic_version.version_num = '2cee32cfc646'; + +-- Running upgrade 54455ce1e9f6 -> 873e18b73036 + +CREATE TYPE buildingtemplatecelldatatype AS ENUM ('ArtDerBaulNutzung', 'ZahlVollgeschosse', 'GRZ', 'GFZ', 'BebauungsArt', 'Bauweise', 'Dachneigung', 'Dachform'); + +CREATE TABLE xp_nutzungsschablone ( + id UUID NOT NULL, + position geometry(POINT,-1), + drehwinkel INTEGER, + skalierung FLOAT, + "spaltenAnz" INTEGER NOT NULL, + "zeilenAnz" INTEGER NOT NULL, + hidden BOOLEAN, + data_attributes buildingtemplatecelldatatype[], + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES xp_po (id) ON DELETE CASCADE +); + +CREATE INDEX idx_xp_nutzungsschablone_position ON xp_nutzungsschablone USING gist (position); + +ALTER TABLE xp_po ADD COLUMN "dientZurDarstellungVon_id" UUID; + +ALTER TABLE xp_po ADD CONSTRAINT fk_po_xp_object FOREIGN KEY("dientZurDarstellungVon_id") REFERENCES xp_objekt (id) ON DELETE CASCADE; + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp";; + +INSERT INTO xp_po(id, type, "dientZurDarstellungVon_id") SELECT uuid_generate_v4(), 'xp_nutzungsschablone', bp_baugebiet.id FROM bp_baugebiet; + +INSERT INTO xp_nutzungsschablone(id, drehwinkel, skalierung, "spaltenAnz", "zeilenAnz", hidden) SELECT xp_po.id, 0, 0.5, 2, 3, TRUE FROM xp_po WHERE xp_po.type = 'xp_nutzungsschablone'; + +create table civil_gemeinde + ( + id uuid not null + primary key, + ags varchar(8) not null, + rs varchar(12), + "gemeindeName" varchar not null, + "ortsteilName" varchar + ); + + create table civil_plan + ( + id uuid not null + primary key, + art bp_planart default 'BPlan'::bp_planart not null, + gemeinde_id uuid + constraint plan_gemeinde_fkey + references civil_gemeinde, + name varchar(255) not null + ); + + create table civil_area + ( + id uuid not null + constraint bp_area_pkey + primary key, + layer varchar(255) not null, + rechtscharakter bp_rechtscharakter default 'Unbekannt'::bp_rechtscharakter, + geom geometry(MultiPolygon) not null, + plan_id uuid + constraint area_plan_fkey + references civil_plan + ); + + create table civil_point + ( + id uuid not null + constraint bp_point_pkey + primary key, + layer varchar(255), + rechtscharakter bp_rechtscharakter default 'Unbekannt'::bp_rechtscharakter, + geom geometry(Point), + plan_id uuid + constraint point_plan_fkey + references civil_plan + ); + + create table civil_line + ( + id uuid not null + constraint bp_line_pkey + primary key, + layer varchar(255), + rechtscharakter bp_rechtscharakter default 'Unbekannt'::bp_rechtscharakter, + geom geometry(MultiLineString), + plan_id uuid + constraint line_plan_fkey + references civil_plan + );; + +UPDATE alembic_version SET version_num='873e18b73036' WHERE alembic_version.version_num = '54455ce1e9f6'; + +-- Running upgrade 873e18b73036 -> 1a015621a38f + +CREATE TYPE fp_rechtscharakter AS ENUM ('Darstellung', 'NachrichtlicheUebernahme', 'Hinweis', 'Vermerk', 'Kennzeichnung', 'Unbekannt'); + +CREATE TABLE fp_objekt ( + id UUID NOT NULL, + rechtscharakter fp_rechtscharakter NOT NULL, + "vonGenehmigungAusgenommen" BOOLEAN, + position geometry(GEOMETRY,-1), + flaechenschluss BOOLEAN, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES xp_objekt (id) ON DELETE CASCADE +); + +CREATE INDEX idx_fp_objekt_position ON fp_objekt USING gist (position); + +CREATE TABLE fp_baugebiet ( + id UUID NOT NULL, + "GFZ" FLOAT, + "GFZmin" FLOAT, + "GFZmax" FLOAT, + "BMZ" FLOAT, + "GRZ" FLOAT, + "allgArtDerBaulNutzung" xp_allgartderbaulnutzung, + "besondereArtDerBaulNutzung" xp_besondereartderbaulnutzung, + "sonderNutzung" xp_sondernutzungen[], + "nutzungText" VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES fp_objekt (id) ON DELETE CASCADE +); + +CREATE TABLE fp_gemeinbedarf ( + id UUID NOT NULL, + zweckbestimmung xp_zweckbestimmunggemeinbedarf, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES fp_objekt (id) ON DELETE CASCADE +); + +CREATE TABLE fp_gewaesser ( + id UUID NOT NULL, + zweckbestimmung xp_zweckbestimmunggewaesser, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES fp_objekt (id) ON DELETE CASCADE +); + +CREATE TABLE fp_gruen ( + id UUID NOT NULL, + zweckbestimmung xp_zweckbestimmunggruen, + nutzungsform xp_nutzungsform, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES fp_objekt (id) ON DELETE CASCADE +); + +CREATE TABLE fp_landwirtschaft ( + id UUID NOT NULL, + zweckbestimmung xp_zweckbestimmunglandwirtschaft, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES fp_objekt (id) ON DELETE CASCADE +); + +CREATE TABLE fp_spiel_sportanlage ( + id UUID NOT NULL, + zweckbestimmung xp_zweckbestimmungspielsportanlage, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES fp_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE fp_zweckbestimmungstrassenverkehr AS ENUM ('Autobahn', 'Hauptverkehrsstrasse', 'Ortsdurchfahrt', 'SonstigerVerkehrswegAnlage', 'VerkehrsberuhigterBereich', 'Platz', 'Fussgaengerbereich', 'RadGehweg', 'Radweg', 'Gehweg', 'Wanderweg', 'ReitKutschweg', 'Rastanlage', 'Busbahnhof', 'UeberfuehrenderVerkehrsweg', 'UnterfuehrenderVerkehrsweg', 'Wirtschaftsweg', 'LandwirtschaftlicherVerkehr', 'RuhenderVerkehr', 'Parkplatz', 'FahrradAbstellplatz', 'P_RAnlage', 'CarSharing', 'BikeSharing', 'B_RAnlage', 'Parkhaus', 'Mischverkehrsflaeche', 'Ladestation', 'Sonstiges'); + +CREATE TABLE fp_strassenverkehr ( + id UUID NOT NULL, + zweckbestimmung fp_zweckbestimmungstrassenverkehr, + nutzungsform xp_nutzungsform, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES fp_objekt (id) ON DELETE CASCADE +); + +CREATE TABLE fp_wald ( + id UUID NOT NULL, + zweckbestimmung xp_zweckbestimmungwald, + eigentumsart xp_eigentumsartwald, + betreten xp_waldbetretungtyp[], + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES fp_objekt (id) ON DELETE CASCADE +); + +UPDATE alembic_version SET version_num='1a015621a38f' WHERE alembic_version.version_num = '873e18b73036'; + +-- Running upgrade 1a015621a38f -> 812b33dce3d1 + +CREATE TYPE so_rechtscharakter AS ENUM ('FestsetzungBPlan', 'DarstellungFPlan', 'InhaltLPlan', 'NachrichtlicheUebernahme', 'Hinweis', 'Vermerk', 'Kennzeichnung', 'Unbekannt', 'Sonstiges'); + +CREATE TABLE so_objekt ( + id UUID NOT NULL, + rechtscharakter so_rechtscharakter NOT NULL, + position geometry(GEOMETRY,-1), + flaechenschluss BOOLEAN, + flussrichtung BOOLEAN, + nordwinkel INTEGER, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES xp_objekt (id) ON DELETE CASCADE +); + +CREATE INDEX idx_so_objekt_position ON so_objekt USING gist (position); + +CREATE TYPE so_klassifiznachdenkmalschutzrecht AS ENUM ('DenkmalschutzEnsemble', 'DenkmalschutzEinzelanlage', 'Grabungsschutzgebiet', 'PufferzoneWeltkulturerbeEnger', 'PufferzoneWeltkulturerbeWeiter', 'ArcheologischesDenkmal', 'Bodendenkmal', 'Sonstiges'); + +CREATE TABLE so_denkmalschutz ( + id UUID NOT NULL, + "artDerFestlegung" so_klassifiznachdenkmalschutzrecht, + weltkulturerbe BOOLEAN, + name VARCHAR, + nummer VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES so_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE so_klassifiznachschienenverkehrsrecht AS ENUM ('Bahnanlage', 'DB_Bahnanlage', 'Personenbahnhof', 'Fernbahnhof', 'Gueterbahnhof', 'Bahnlinie', 'Personenbahnlinie', 'Regionalbahn', 'Kleinbahn', 'Gueterbahnlinie', 'WerksHafenbahn', 'Seilbahn', 'OEPNV', 'Strassenbahn', 'UBahn', 'SBahn', 'OEPNV_Haltestelle', 'Sonstiges'); + +CREATE TABLE so_schienenverkehr ( + id UUID NOT NULL, + "artDerFestlegung" so_klassifiznachschienenverkehrsrecht, + name VARCHAR, + nummer VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES so_objekt (id) ON DELETE CASCADE +); + +CREATE TYPE so_klassifizschutzgebietwasserrecht AS ENUM ('Wasserschutzgebiet', 'QuellGrundwasserSchutzgebiet', 'OberflaechengewaesserSchutzgebiet', 'Heilquellenschutzgebiet', 'Sonstiges'); + +CREATE TYPE so_schutzzonenwasserrecht AS ENUM ('Zone_1', 'Zone_2', 'Zone_3', 'Zone_3a', 'Zone_3b', 'Zone_4'); + +CREATE TABLE so_wasserschutz ( + id UUID NOT NULL, + "artDerFestlegung" so_klassifizschutzgebietwasserrecht, + zone so_schutzzonenwasserrecht, + name VARCHAR, + nummer VARCHAR, + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES so_objekt (id) ON DELETE CASCADE +); + +UPDATE bp_baugebiet SET sondernutzung=NULL WHERE sondernutzung='KeineSondernutzung'::xp_sondernutzungen;; + +ALTER TABLE bp_baugebiet ALTER sondernutzung DROP DEFAULT, ALTER sondernutzung type xp_sondernutzungen[] using NULLIF(ARRAY[sondernutzung], '{null}'), alter sondernutzung set default '{}';; + +UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_Naturschutz_Landschaft', 'BP_Naturschutz_Landschaftsbild_Naturhaushalt');; + +UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_GruenFlaeche', 'BP_Landwirtschaft_Wald_und_Gruenflaechen');; + +UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_WaldFlaeche', 'BP_Landwirtschaft_Wald_und_Gruenflaechen');; + +UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_GemeinbedarfsFlaeche', 'BP_Gemeinbedarf_Spiel_und_Sportanlagen');; + +UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_Erhaltungssatzung_und_Denkmalschutz', 'SO_SonstigeGebiete');; + +UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_VerEntsorgung', 'BP_Ver_und_Entsorgung');; + +UPDATE xp_ppo SET symbol_path = replace(symbol_path, 'BP_SpielSportanlagenFlaeche', 'BP_Gemeinbedarf_Spiel_und_Sportanlagen');; + +UPDATE alembic_version SET version_num='812b33dce3d1' WHERE alembic_version.version_num = '1a015621a38f'; + +-- Running upgrade 812b33dce3d1 -> 0a6f00a1f663 + +UPDATE xp_po SET type='xp_pto' WHERE type='xp_tpo';; + +ALTER INDEX idx_xp_tpo_position RENAME TO idx_xp_pto_position;; + +ALTER TABLE xp_tpo RENAME CONSTRAINT xp_tpo_id_fkey TO xp_pto_id_fkey;; + +ALTER TABLE xp_tpo RENAME CONSTRAINT xp_tpo_pkey TO xp_pto_pkey;; + +ALTER TABLE xp_tpo RENAME TO xp_pto;; + +ALTER TABLE xp_ppo ALTER COLUMN drehwinkel TYPE FLOAT USING drehwinkel::float;; + +ALTER TABLE xp_pto ALTER COLUMN drehwinkel TYPE FLOAT USING drehwinkel::float;; + +ALTER TABLE xp_nutzungsschablone ALTER COLUMN drehwinkel TYPE FLOAT USING drehwinkel::float;; + +ALTER TABLE xp_objekt ALTER COLUMN drehwinkel TYPE FLOAT USING drehwinkel::float;; + +INSERT INTO xp_externe_referenz (id, datum, "referenzMimeType", art, "referenzName", beschreibung, file, "georefURL", "referenzURL") SELECT id, datum, "referenzMimeType", art, "referenzName", beschreibung, file, "georefURL", "referenzURL" FROM xp_spez_externe_referenz; + +ALTER TABLE xp_spez_externe_referenz ADD CONSTRAINT xp_spez_externe_referenz_id_fkey FOREIGN KEY(id) REFERENCES xp_externe_referenz (id) ON DELETE CASCADE; + +ALTER TABLE xp_spez_externe_referenz DROP COLUMN datum; + +ALTER TABLE xp_spez_externe_referenz DROP COLUMN "referenzMimeType"; + +ALTER TABLE xp_spez_externe_referenz DROP COLUMN art; + +ALTER TABLE xp_spez_externe_referenz DROP COLUMN "referenzName"; + +ALTER TABLE xp_spez_externe_referenz DROP COLUMN beschreibung; + +ALTER TABLE xp_spez_externe_referenz DROP COLUMN file; + +ALTER TABLE xp_spez_externe_referenz DROP COLUMN "georefURL"; + +ALTER TABLE xp_spez_externe_referenz DROP COLUMN "referenzURL"; + +ALTER TABLE xp_externe_referenz ADD COLUMN type VARCHAR(50); + +UPDATE xp_externe_referenz SET type='xp_spez_externe_referenz' WHERE id in (SELECT id FROM xp_spez_externe_referenz);; + +UPDATE xp_externe_referenz SET type='xp_externe_referenz' WHERE type IS NULL; + +UPDATE alembic_version SET version_num='0a6f00a1f663' WHERE alembic_version.version_num = '812b33dce3d1'; + +-- Running upgrade 0a6f00a1f663 -> b93ec8372df6 + +ALTER TABLE xp_plan ALTER COLUMN "raeumlicherGeltungsbereich" type geometry(Geometry);; + +ALTER TABLE xp_plan ADD CONSTRAINT check_geometry_type CHECK (st_dimension("raeumlicherGeltungsbereich") = 2);; + +ALTER TABLE xp_bereich ALTER COLUMN "geltungsbereich" type geometry(Geometry);; + +ALTER TABLE xp_bereich ADD CONSTRAINT check_geometry_type CHECK (st_dimension("geltungsbereich") = 2);; + +UPDATE alembic_version SET version_num='b93ec8372df6' WHERE alembic_version.version_num = '0a6f00a1f663'; + +-- Running upgrade b93ec8372df6 -> fb27f7a59e17 + +ALTER TABLE xp_plan ADD COLUMN hoehenbezug VARCHAR;; + +create or replace function xp_plan_sync_attr_hoehenbezug() + returns trigger language plpgsql as $$ + begin + UPDATE xp_plan + SET hoehenbezug = COALESCE(NEW.hoehenbezug, hoehenbezug) + where xp_plan.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger xp_plan_sync_attr_hoehenbezug + after insert or update on bp_plan + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure xp_plan_sync_attr_hoehenbezug(); + + create or replace function bp_plan_sync_attr_hoehenbezug() + returns trigger language plpgsql as $$ + begin + UPDATE bp_plan + SET hoehenbezug = COALESCE(NEW.hoehenbezug, hoehenbezug) + where bp_plan.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger bp_plan_sync_attr_hoehenbezug + after insert or update on xp_plan + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_plan_sync_attr_hoehenbezug();; + +ALTER TABLE xp_objekt DROP COLUMN "gesetzlicheGrundlage"; + +DROP TYPE xp_gesetzliche_grundlage CASCADE; + +ALTER TABLE xp_objekt ADD COLUMN "gesetzlicheGrundlage_id" UUID; + +CREATE TABLE xp_gesetzliche_grundlage ( + id UUID NOT NULL, + name VARCHAR, + datum DATE, + detail VARCHAR, + PRIMARY KEY (id) +); + +ALTER TYPE xp_externereferenztyp ADD VALUE 'Schutzgebietsverordnung';; + +CREATE TYPE xp_traegerschaft AS ENUM('EinrichtungBund','EinrichtungLand', 'EinrichtungKreis', 'KommunaleEinrichtung', 'ReligioeserTraeger', 'SonstTraeger');; + +ALTER TABLE bp_gemeinbedarf ADD COLUMN traeger xp_traegerschaft;; + +UPDATE xp_externe_referenz SET "referenzName"='Unbekannt' WHERE "referenzName" is NULL;; + +UPDATE xp_externe_referenz SET "referenzURL"="referenzURL" WHERE "referenzURL" is NULL;; + +ALTER TYPE xp_sondernutzungen ADD VALUE 'SondergebietGrosshandel';; + +ALTER TYPE xp_spemassnahmentypen ADD VALUE 'ArtenreicherGehoelzbestand';; + +COMMIT; + +UPDATE xp_spe_daten SET "klassifizMassnahme" = 'ArtenreicherGehoelzbestand'::xp_spemassnahmentypen WHERE "klassifizMassnahme" = 'ArtentreicherGehoelzbestand'::xp_spemassnahmentypen;; + +BEGIN; + +ALTER TYPE xp_spemassnahmentypen ADD VALUE 'Moor';; + +ALTER TABLE bp_bereich ADD COLUMN verfahren bp_verfahren;; + +UPDATE bp_bereich SET verfahren = bp_plan.verfahren FROM bp_plan WHERE "gehoertZuPlan_id" = bp_plan.id;; + +create or replace function bp_bereich_sync_attr_verfahren() + returns trigger language plpgsql as $$ + begin + UPDATE bp_bereich + SET verfahren = COALESCE(NEW.verfahren, bp_bereich.verfahren) + where bp_bereich.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger bp_bereich_sync_attr_verfahren + after insert or update on bp_plan + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_bereich_sync_attr_verfahren(); + + create or replace function bp_plan_sync_attr_verfahren() + returns trigger language plpgsql as $$ + begin + UPDATE bp_plan + SET verfahren = COALESCE(NEW.verfahren, verfahren) + where bp_plan.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger bp_plan_sync_attr_verfahren + after insert or update on bp_bereich + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_plan_sync_attr_verfahren();; + +CREATE TYPE xp_rechtscharakter AS ENUM ( + 'FestsetzungBPlan', + 'NachrichtlicheUebernahme', + 'DarstellungFPlan', + 'ZielDerRaumordnung', + 'GrundsatzDerRaumordnung', + 'NachrichtlicheUebernahmeZiel', + 'NachrichtlicheUebernahmeGrundsatz', + 'NurInformationsgehaltRPlan', + 'TextlichesZielRaumordnung', + 'ZielUndGrundsatzRaumordnung', + 'VorschlagRaumordnung', + 'FestsetzungImLP', + 'GeplanteFestsetzungImLP', + 'DarstellungKennzeichnungImLP', + 'LandschaftsplanungsInhaltZurBeruecksichtigung', + 'Hinweis', + 'Kennzeichnung', + 'Vermerk', + 'Unbekannt', + 'Sonstiges'); + + ALTER TABLE xp_objekt ADD COLUMN rechtscharakter xp_rechtscharakter; + -- bp_objekt + UPDATE xp_objekt SET rechtscharakter = ( + CASE + WHEN bp_objekt.rechtscharakter = 'Festsetzung'::bp_rechtscharakter + THEN 'FestsetzungBPlan'::xp_rechtscharakter + ELSE bp_objekt.rechtscharakter::text::xp_rechtscharakter + END) + FROM bp_objekt WHERE xp_objekt.id = bp_objekt.id; + + create or replace function xp_objekt_sync_attr_rechtscharakter() + returns trigger language plpgsql as $$ + begin + UPDATE xp_objekt + SET rechtscharakter = ( + CASE + WHEN NEW.rechtscharakter = 'Festsetzung'::bp_rechtscharakter + THEN 'FestsetzungBPlan'::xp_rechtscharakter + ELSE COALESCE(NEW.rechtscharakter::text::xp_rechtscharakter, rechtscharakter) + END) + where xp_objekt.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger xp_objekt_sync_attr_rechtscharakter + after insert or update on bp_objekt + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure xp_objekt_sync_attr_rechtscharakter(); + + create or replace function bp_objekt_sync_attr_rechtscharakter() + returns trigger language plpgsql as $$ + begin + IF new.type != 'bp_objekt' + THEN RETURN NEW; + END IF; + UPDATE bp_objekt + SET rechtscharakter = ( + CASE + WHEN NEW.rechtscharakter = 'FestsetzungBPlan'::xp_rechtscharakter + THEN 'Festsetzung'::bp_rechtscharakter + WHEN NEW.rechtscharakter in ('NachrichtlicheUebernahme'::xp_rechtscharakter, + 'Hinweis'::xp_rechtscharakter, + 'Vermerk'::xp_rechtscharakter, + 'Kennzeichnung'::xp_rechtscharakter, + 'Unbekannt'::xp_rechtscharakter) + THEN NEW.rechtscharakter::text::bp_rechtscharakter + ELSE rechtscharakter + END) + where bp_objekt.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger bp_objekt_sync_attr_hoehenbezug + after insert or update on xp_objekt + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_objekt_sync_attr_rechtscharakter(); + + + -- fp_objekt + UPDATE xp_objekt SET rechtscharakter = ( + CASE + WHEN fp_objekt.rechtscharakter = 'Darstellung'::fp_rechtscharakter + THEN 'DarstellungFPlan'::xp_rechtscharakter + ELSE fp_objekt.rechtscharakter::text::xp_rechtscharakter + END) + FROM fp_objekt WHERE xp_objekt.id = fp_objekt.id; + + create or replace function xp_objekt_sync_attr_rechtscharakter_from_fp() + returns trigger language plpgsql as $$ + begin + UPDATE xp_objekt + SET rechtscharakter = ( + CASE + WHEN NEW.rechtscharakter = 'Darstellung'::fp_rechtscharakter + THEN 'DarstellungFPlan'::xp_rechtscharakter + ELSE COALESCE(NEW.rechtscharakter::text::xp_rechtscharakter, rechtscharakter) + END) + where xp_objekt.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger xp_objekt_sync_attr_rechtscharakter_from_fp + after insert or update on fp_objekt + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure xp_objekt_sync_attr_rechtscharakter_from_fp(); + + create or replace function fp_objekt_sync_attr_rechtscharakter() + returns trigger language plpgsql as $$ + begin + IF new.type != 'fp_objekt' + THEN RETURN NEW; + END IF; + UPDATE fp_objekt + SET rechtscharakter = ( + CASE + WHEN NEW.rechtscharakter = 'DarstellungFPlan'::xp_rechtscharakter + THEN 'Darstellung'::fp_rechtscharakter + WHEN NEW.rechtscharakter in ('NachrichtlicheUebernahme'::xp_rechtscharakter, + 'Hinweis'::xp_rechtscharakter, + 'Vermerk'::xp_rechtscharakter, + 'Kennzeichnung'::xp_rechtscharakter, + 'Unbekannt'::xp_rechtscharakter) + THEN NEW.rechtscharakter::text::fp_rechtscharakter + ELSE rechtscharakter + END) + where fp_objekt.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger fp_objekt_sync_attr_rechtscharakter + after insert or update on xp_objekt + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_objekt_sync_attr_rechtscharakter(); + + + -- so_objekt + UPDATE xp_objekt SET rechtscharakter = ( + CASE + WHEN so_objekt.rechtscharakter = 'InhaltLPlan'::so_rechtscharakter + THEN 'FestsetzungImLP'::xp_rechtscharakter + ELSE so_objekt.rechtscharakter::text::xp_rechtscharakter + END) + FROM so_objekt WHERE xp_objekt.id = so_objekt.id; + + create or replace function xp_objekt_sync_attr_rechtscharakter_from_so() + returns trigger language plpgsql as $$ + begin + UPDATE xp_objekt + SET rechtscharakter = ( + CASE + WHEN NEW.rechtscharakter = 'InhaltLPlan'::so_rechtscharakter + THEN 'FestsetzungImLP'::xp_rechtscharakter + ELSE COALESCE(NEW.rechtscharakter::text::xp_rechtscharakter, rechtscharakter) + END) + where xp_objekt.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger xp_objekt_sync_attr_rechtscharakter_from_so + after insert or update on so_objekt + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure xp_objekt_sync_attr_rechtscharakter_from_so(); + + create or replace function so_objekt_sync_attr_rechtscharakter() + returns trigger language plpgsql as $$ + begin + IF new.type != 'so_objekt' + THEN RETURN NEW; + END IF; + UPDATE so_objekt + SET rechtscharakter = ( + CASE + WHEN NEW.rechtscharakter = 'FestsetzungImLP'::xp_rechtscharakter + THEN 'InhaltLPlan'::so_rechtscharakter + WHEN NEW.rechtscharakter in ('NachrichtlicheUebernahme'::xp_rechtscharakter, + 'Hinweis'::xp_rechtscharakter, + 'Vermerk'::xp_rechtscharakter, + 'Kennzeichnung'::xp_rechtscharakter, + 'Unbekannt'::xp_rechtscharakter, + 'Sonstiges'::xp_rechtscharakter) + THEN NEW.rechtscharakter::text::so_rechtscharakter + ELSE rechtscharakter + END) + where so_objekt.id = new.id; + RETURN NEW; + end $$; + + create constraint trigger so_objekt_sync_attr_rechtscharakter + after insert or update on xp_objekt + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure so_objekt_sync_attr_rechtscharakter();; + +ALTER TYPE bp_planart ADD VALUE 'BebauungsplanZurWohnraumversorgung';; + +ALTER TYPE xp_zweckbestimmunggruen ADD VALUE 'Naturerfahrungsraum';; + +ALTER TYPE xp_besondereartderbaulnutzung ADD VALUE 'DoerflichesWohngebiet';; + +CREATE TABLE bp_zweckbestimmung_gruen ( + id UUID NOT NULL, + allgemein xp_zweckbestimmunggruen, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + gruenflaeche_id UUID, + FOREIGN KEY(gruenflaeche_id) REFERENCES bp_gruenflaeche (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM bp_gruenflaeche g + ) + INSERT INTO bp_zweckbestimmung_gruen (id, allgemein, gruenflaeche_id) + SELECT * FROM upd; + + create or replace function bp_gruenflaeche_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE bp_zweckbestimmung_gruen + SET allgemein = NEW.zweckbestimmung + WHERE gruenflaeche_id = NEW.id; + ELSE + UPDATE bp_gruenflaeche SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.gruenflaeche_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_gruenflaeche_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on bp_gruenflaeche + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_gruenflaeche_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger bp_gruenflaeche_sync_attr_zweckbestimmung + after insert or update of allgemein on bp_zweckbestimmung_gruen + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_gruenflaeche_sync_attr_zweckbestimmung('gruenflaeche_id');; + +CREATE TABLE bp_komplexe_sondernutzung ( + id UUID NOT NULL, + allgemein xp_sondernutzungen, + "nutzungText" VARCHAR, + aufschrift VARCHAR, + baugebiet_id UUID, + FOREIGN KEY(baugebiet_id) REFERENCES bp_baugebiet (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + INSERT INTO bp_komplexe_sondernutzung (id, baugebiet_id, allgemein) + SELECT uuid_generate_v4(), bp_baugebiet.id, t.sondernutzung_unnested + FROM bp_baugebiet + CROSS JOIN unnest(bp_baugebiet.sondernutzung) as t(sondernutzung_unnested); + + create or replace function bp_baugebiet_sync_attr_sondernutzung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'sondernutzung' THEN + DELETE FROM bp_komplexe_sondernutzung WHERE baugebiet_id = NEW.id; + + INSERT INTO bp_komplexe_sondernutzung (id, baugebiet_id, allgemein) + SELECT uuid_generate_v4(), bp_baugebiet.id, t.sondernutzung_unnested + FROM bp_baugebiet + CROSS JOIN unnest(bp_baugebiet.sondernutzung) as t(sondernutzung_unnested); + ELSE + -- make sure no duplicates are inserted by selecting distinct from unnested array + UPDATE bp_baugebiet SET sondernutzung = ARRAY(SELECT DISTINCT UNNEST(sondernutzung || ARRAY[NEW.allgemein])) + WHERE id = NEW.baugebiet_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_baugebiet_sync_attr_sondernutzung + after insert or update of sondernutzung on bp_baugebiet + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_baugebiet_sync_attr_sondernutzung('sondernutzung'); + + create constraint trigger bp_baugebiet_sync_attr_sondernutzung + after insert or update of allgemein on bp_komplexe_sondernutzung + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_baugebiet_sync_attr_sondernutzung('baugebiet_id'); + + create or replace function bp_baugebiet_sync_attr_sondernutzung_on_delete() + returns trigger language plpgsql as $$ + begin + -- make sure no duplicates are inserted by selecting distinct from unnested array + UPDATE bp_baugebiet SET sondernutzung = array_remove(sondernutzung, OLD.allgemein) + WHERE id = OLD.baugebiet_id; + RETURN NULL; + end $$; + + create constraint trigger bp_baugebiet_sync_attr_sondernutzung_on_delete + after delete on bp_komplexe_sondernutzung + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_baugebiet_sync_attr_sondernutzung_on_delete();; + +CREATE TYPE so_zweckbestimmungstrassenverkehr AS ENUM ( + 'AutobahnUndAehnlich', + 'Hauptverkehrsstrasse', + 'SonstigerVerkehrswegAnlage', + 'VerkehrsberuhigterBereich', + 'Platz', + 'Fussgaengerbereich', + 'RadGehweg', + 'Radweg', + 'Gehweg', + 'Wanderweg', + 'ReitKutschweg', + 'Rastanlage', + 'Busbahnhof', + 'UeberfuehrenderVerkehrsweg', + 'UnterfuehrenderVerkehrsweg', + 'Wirtschaftsweg', + 'LandwirtschaftlicherVerkehr', + 'Anschlussflaeche', + 'Verkehrsgruen', + 'RuhenderVerkehr', + 'Parkplatz', + 'FahrradAbstellplatz', + 'P_RAnlage', + 'B_RAnlage', + 'Parkhaus', + 'CarSharing', + 'BikeSharing', + 'Mischverkehrsflaeche', + 'Ladestation', + 'Sonstiges'); + + CREATE TYPE so_strasseneinteilung AS ENUM ( + 'Bundesautobahn', + 'Bundesstrasse', + 'LandesStaatsstrasse', + 'Kreisstrasse', + 'Gemeindestrasse', + 'SonstOeffentlStrasse'); + + + CREATE TABLE so_strassenverkehr ( + id UUID NOT NULL, + "MaxZahlWohnungen" INTEGER, + "MinGRWohneinheit" FLOAT, + "Fmin" FLOAT, + "Fmax" FLOAT, + "Bmin" FLOAT, + "Bmax" FLOAT, + "Tmin" FLOAT, + "Tmax" FLOAT, + "GFZmin" FLOAT, + "GFZmax" FLOAT, + "GFZ" FLOAT, + "GFZ_Ausn" FLOAT, + "GFmin" FLOAT, + "GFmax" FLOAT, + "GF" FLOAT, + "GF_Ausn" FLOAT, + "BMZ" FLOAT, + "BMZ_Ausn" FLOAT, + "BM" FLOAT, + "BM_Ausn" FLOAT, + "GRZmin" FLOAT, + "GRZmax" FLOAT, + "GRZ" FLOAT, + "GRZ_Ausn" FLOAT, + "GRmin" FLOAT, + "GRmax" FLOAT, + "GR" FLOAT, + "GR_Ausn" FLOAT, + "Zmin" INTEGER, + "Zmax" INTEGER, + "Zzwingend" INTEGER, + "Z" INTEGER, + "Z_Ausn" INTEGER, + "Z_Staffel" INTEGER, + "Z_Dach" INTEGER, + "ZUmin" INTEGER, + "ZUmax" INTEGER, + "ZUzwingend" INTEGER, + "ZU" INTEGER, + "ZU_Ausn" INTEGER, + + einteilung so_strasseneinteilung, + name VARCHAR, + nummer VARCHAR, + "istOrtsdurchfahrt" BOOLEAN, + nutzungsform xp_nutzungsform not null, + "zugunstenVon" VARCHAR, + "hatDarstellungMitBesondZweckbest" BOOLEAN, + + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES so_objekt (id) ON DELETE CASCADE + ); + + CREATE TABLE so_zweckbestimmung_strassenverkehr ( + id UUID NOT NULL, + allgemein so_zweckbestimmungstrassenverkehr, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + strassenverkehr_id UUID, + FOREIGN KEY(strassenverkehr_id) REFERENCES so_strassenverkehr (id) ON DELETE CASCADE, + PRIMARY KEY (id) + );; + +ALTER TABLE so_objekt ALTER COLUMN nordwinkel TYPE FLOAT USING nordwinkel::float;; + +ALTER TABLE fp_gemeinbedarf ALTER zweckbestimmung DROP DEFAULT, ALTER zweckbestimmung type xp_zweckbestimmunggemeinbedarf[] using NULLIF(ARRAY[zweckbestimmung], '{null}'), alter zweckbestimmung set default '{}';; + +ALTER TYPE bp_rechtsstand ADD VALUE 'Entwurfsbeschluss'; + ALTER TYPE bp_rechtsstand ADD VALUE 'TeilweiseAufgehoben'; + ALTER TYPE bp_rechtsstand ADD VALUE 'TeilweiseAusserKraft'; + + ALTER TYPE rp_rechtsstand ADD VALUE 'TeilweiseAusserKraft'; + ALTER TYPE fp_rechtsstand ADD VALUE 'Entwurfsbeschluss';; + +CREATE TABLE bp_zweckbestimmung_sport ( + id UUID NOT NULL, + allgemein xp_zweckbestimmungspielsportanlage, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + spiel_sportanlage_id UUID, + FOREIGN KEY(spiel_sportanlage_id) REFERENCES bp_spiel_sportanlage (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM bp_spiel_sportanlage g + ) + INSERT INTO bp_zweckbestimmung_sport (id, allgemein, spiel_sportanlage_id) + SELECT * FROM upd; + + create or replace function bp_spiel_sportanlage_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE bp_zweckbestimmung_sport + SET allgemein = NEW.zweckbestimmung + WHERE spiel_sportanlage_id = NEW.id; + ELSE + UPDATE bp_spiel_sportanlage SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.spiel_sportanlage_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_spiel_sportanlage_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on bp_spiel_sportanlage + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_spiel_sportanlage_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger bp_spiel_sportanlage_sync_attr_zweckbestimmung + after insert or update of allgemein on bp_zweckbestimmung_sport + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_spiel_sportanlage_sync_attr_zweckbestimmung('spiel_sportanlage_id');; + +CREATE TABLE bp_zweckbestimmung_gemeinbedarf ( + id UUID NOT NULL, + allgemein xp_zweckbestimmunggemeinbedarf, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + gemeinbedarf_id UUID, + FOREIGN KEY(gemeinbedarf_id) REFERENCES bp_gemeinbedarf (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM bp_gemeinbedarf g + ) + INSERT INTO bp_zweckbestimmung_gemeinbedarf (id, allgemein, gemeinbedarf_id) + SELECT * FROM upd; + + create or replace function bp_gemeinbedarf_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE bp_zweckbestimmung_gemeinbedarf + SET allgemein = NEW.zweckbestimmung + WHERE gemeinbedarf_id = NEW.id; + ELSE + UPDATE bp_gemeinbedarf SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.gemeinbedarf_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_gemeinbedarf_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on bp_gemeinbedarf + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_gemeinbedarf_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger bp_gemeinbedarf_sync_attr_zweckbestimmung + after insert or update of allgemein on bp_zweckbestimmung_gemeinbedarf + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_gemeinbedarf_sync_attr_zweckbestimmung('gemeinbedarf_id');; + +CREATE TABLE bp_zweckbestimmung_landwirtschaft ( + id UUID NOT NULL, + allgemein xp_zweckbestimmunglandwirtschaft, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + landwirtschaft_id UUID, + FOREIGN KEY(landwirtschaft_id) REFERENCES bp_landwirtschaft (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM bp_landwirtschaft g + ) + INSERT INTO bp_zweckbestimmung_landwirtschaft (id, allgemein, landwirtschaft_id) + SELECT * FROM upd; + + create or replace function bp_landwirtschaft_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE bp_zweckbestimmung_landwirtschaft + SET allgemein = NEW.zweckbestimmung + WHERE landwirtschaft_id = NEW.id; + ELSE + UPDATE bp_landwirtschaft SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.landwirtschaft_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_landwirtschaft_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on bp_landwirtschaft + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_landwirtschaft_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger bp_landwirtschaft_sync_attr_zweckbestimmung + after insert or update of allgemein on bp_zweckbestimmung_landwirtschaft + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_landwirtschaft_sync_attr_zweckbestimmung('landwirtschaft_id');; + +CREATE TABLE bp_zweckbestimmung_wald ( + id UUID NOT NULL, + allgemein xp_zweckbestimmungwald, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + waldflaeche_id UUID, + FOREIGN KEY(waldflaeche_id) REFERENCES bp_wald (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM bp_wald g + ) + INSERT INTO bp_zweckbestimmung_wald (id, allgemein, waldflaeche_id) + SELECT * FROM upd; + + create or replace function bp_wald_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE bp_zweckbestimmung_wald + SET allgemein = NEW.zweckbestimmung + WHERE waldflaeche_id = NEW.id; + ELSE + UPDATE bp_wald SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.waldflaeche_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_wald_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on bp_wald + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_wald_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger bp_wald_sync_attr_zweckbestimmung + after insert or update of allgemein on bp_zweckbestimmung_wald + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_wald_sync_attr_zweckbestimmung('waldflaeche_id');; + +ALTER TABLE bp_pflanzung ADD COLUMN "pflanzenArt" VARCHAR; + +CREATE TABLE bp_zweckbestimmung_versorgung ( + id UUID NOT NULL, + allgemein xp_zweckbestimmungverentsorgung, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + versorgung_id UUID, + FOREIGN KEY(versorgung_id) REFERENCES bp_versorgung (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM bp_versorgung g + ) + INSERT INTO bp_zweckbestimmung_versorgung (id, allgemein, versorgung_id) + SELECT * FROM upd; + + create or replace function bp_versorgung_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE bp_zweckbestimmung_versorgung + SET allgemein = NEW.zweckbestimmung + WHERE versorgung_id = NEW.id; + ELSE + UPDATE bp_versorgung SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.versorgung_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger bp_versorgung_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on bp_versorgung + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_versorgung_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger bp_versorgung_sync_attr_zweckbestimmung + after insert or update of allgemein on bp_zweckbestimmung_versorgung + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure bp_versorgung_sync_attr_zweckbestimmung('versorgung_id');; + +CREATE TYPE bp_verlaengerungveraenderungssperre AS ENUM ('Keine', 'ErsteVerlaengerung', 'ZweiteVerlaengerung'); + + CREATE TABLE bp_veraenderungssperre_daten ( + id UUID NOT NULL, + "startDatum" DATE not null, + "endDatum" DATE not null, + verlaengerung bp_verlaengerungveraenderungssperre, + "beschlussDatum" DATE, + + plan_id UUID, + FOREIGN KEY(plan_id) REFERENCES bp_plan (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + ALTER TABLE xp_externe_referenz ADD COLUMN veraenderungssperre_id UUID REFERENCES bp_veraenderungssperre_daten(id); + + WITH upd AS ( + SELECT uuid_generate_v4(), "veraenderungssperreDatum", + "veraenderungssperreEndDatum", "verlaengerungVeraenderungssperre"::text::bp_verlaengerungveraenderungssperre AS verl, + "veraenderungssperreBeschlussDatum", id FROM bp_plan p + ) + INSERT INTO bp_veraenderungssperre_daten (id, "startDatum", "endDatum", verlaengerung, "beschlussDatum", plan_id) + SELECT * FROM upd + WHERE upd."veraenderungssperreEndDatum" is not NULL AND "veraenderungssperreDatum" is not NULL AND upd.verl is not NULL;; + +CREATE TYPE so_klassifizgewaesser AS ENUM ( + 'Gewaesser', + 'Fliessgewaesser', + 'Gewaesser1Ordnung', + 'Gewaesser2Ordnung', + 'Gewaesser3Ordnung', + 'StehendesGewaesser', + 'Hafen', + 'Sportboothafen', + 'Wasserstrasse', + 'Kanal', + 'Sonstiges'); + + CREATE TABLE so_gewaesser ( + id UUID NOT NULL, + name VARCHAR, + nummer VARCHAR, + + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES so_objekt (id) ON DELETE CASCADE + ); + + CREATE TABLE so_festlegung_gewaesser ( + id UUID NOT NULL, + allgemein so_klassifizgewaesser, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + gewaesser_id UUID, + FOREIGN KEY(gewaesser_id) REFERENCES so_gewaesser (id) ON DELETE CASCADE, + PRIMARY KEY (id) + );; + +CREATE TYPE so_klassifizwasserwirtschaft AS ENUM ( + 'HochwasserRueckhaltebecken', + 'Ueberschwemmgebiet', + 'Versickerungsflaeche', + 'Entwaesserungsgraben', + 'Deich', + 'RegenRueckhaltebecken', + 'Sonstiges'); + + CREATE TABLE so_wasserwirtschaft ( + id UUID NOT NULL, + + "artDerFestlegung" so_klassifizwasserwirtschaft, + + PRIMARY KEY (id), + FOREIGN KEY(id) REFERENCES so_objekt (id) ON DELETE CASCADE + );; + +ALTER TABLE fp_plan ADD COLUMN "versionBauNVO_id" uuid REFERENCES xp_gesetzliche_grundlage(id); + ALTER TABLE fp_plan ADD COLUMN "versionBauGB_id" uuid REFERENCES xp_gesetzliche_grundlage(id); + ALTER TABLE fp_plan ADD COLUMN "versionSonstRechtsgrundlage_id" uuid REFERENCES xp_gesetzliche_grundlage(id);; + +ALTER TABLE fp_baugebiet ADD COLUMN "GFZdurchschnittlich" FLOAT; + ALTER TABLE fp_baugebiet ADD COLUMN "abweichungBauNVO" xp_abweichungbaunvotypen;; + +CREATE TABLE fp_komplexe_sondernutzung ( + id UUID NOT NULL, + allgemein xp_sondernutzungen, + "nutzungText" VARCHAR, + aufschrift VARCHAR, + baugebiet_id UUID, + FOREIGN KEY(baugebiet_id) REFERENCES fp_baugebiet (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + INSERT INTO fp_komplexe_sondernutzung (id, baugebiet_id, allgemein) + SELECT uuid_generate_v4(), fp_baugebiet.id, t.sondernutzung_unnested + FROM fp_baugebiet + CROSS JOIN unnest(fp_baugebiet."sonderNutzung") as t(sondernutzung_unnested); + + create or replace function fp_baugebiet_sync_attr_sondernutzung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'sondernutzung' THEN + DELETE FROM fp_komplexe_sondernutzung WHERE baugebiet_id = NEW.id; + + INSERT INTO fp_komplexe_sondernutzung (id, baugebiet_id, allgemein) + SELECT uuid_generate_v4(), fp_baugebiet.id, t.sondernutzung_unnested + FROM fp_baugebiet + CROSS JOIN unnest(fp_baugebiet."sonderNutzung") as t(sondernutzung_unnested); + ELSE + -- make sure no duplicates are inserted by selecting distinct from unnested array + UPDATE fp_baugebiet SET "sonderNutzung" = ARRAY(SELECT DISTINCT UNNEST("sonderNutzung" || ARRAY[NEW.allgemein])) + WHERE id = NEW.baugebiet_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger fp_baugebiet_sync_attr_sondernutzung + after insert or update of "sonderNutzung" on fp_baugebiet + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_baugebiet_sync_attr_sondernutzung('sondernutzung'); + + create constraint trigger fp_baugebiet_sync_attr_sondernutzung + after insert or update of allgemein on fp_komplexe_sondernutzung + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_baugebiet_sync_attr_sondernutzung('baugebiet_id'); + + create or replace function fp_baugebiet_sync_attr_sondernutzung_on_delete() + returns trigger language plpgsql as $$ + begin + -- make sure no duplicates are inserted by selecting distinct from unnested array + UPDATE fp_baugebiet SET "sonderNutzung" = array_remove("sonderNutzung", OLD.allgemein) + WHERE id = OLD.baugebiet_id; + RETURN NULL; + end $$; + + create constraint trigger fp_baugebiet_sync_attr_sondernutzung_on_delete + after delete on fp_komplexe_sondernutzung + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_baugebiet_sync_attr_sondernutzung_on_delete();; + +CREATE TABLE fp_zweckbestimmung_gemeinbedarf ( + id UUID NOT NULL, + allgemein xp_zweckbestimmunggemeinbedarf, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + gemeinbedarf_id UUID, + FOREIGN KEY(gemeinbedarf_id) REFERENCES fp_gemeinbedarf (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + ALTER TABLE fp_gemeinbedarf ADD COLUMN traeger xp_traegerschaft; + ALTER TABLE fp_gemeinbedarf ADD COLUMN "zugunstenVon" VARCHAR; + + INSERT INTO fp_zweckbestimmung_gemeinbedarf (id, gemeinbedarf_id, allgemein) + SELECT uuid_generate_v4(), fp_gemeinbedarf.id, t.zweckbestimmung_unnested + FROM fp_gemeinbedarf + CROSS JOIN unnest(fp_gemeinbedarf."zweckbestimmung") as t(zweckbestimmung_unnested); + + create or replace function fp_gemeinbedarf_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmung' THEN + DELETE FROM fp_zweckbestimmung_gemeinbedarf WHERE gemeinbedarf_id = NEW.id; + + INSERT INTO fp_zweckbestimmung_gemeinbedarf (id, gemeinbedarf_id, allgemein) + SELECT uuid_generate_v4(), fp_gemeinbedarf.id, t.zweckbestimmung_unnested + FROM fp_gemeinbedarf + CROSS JOIN unnest(fp_gemeinbedarf."zweckbestimmung") as t(zweckbestimmung_unnested); + ELSE + UPDATE fp_gemeinbedarf SET zweckbestimmung = array_remove(zweckbestimmung, OLD.allgemein) WHERE id = OLD.gemeinbedarf_id; + + -- make sure no duplicates are inserted by selecting distinct from unnested array + UPDATE fp_gemeinbedarf SET zweckbestimmung = ARRAY(SELECT DISTINCT UNNEST(zweckbestimmung || ARRAY[NEW.allgemein])) + WHERE id = NEW.gemeinbedarf_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger fp_gemeinbedarf_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on fp_gemeinbedarf + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_gemeinbedarf_sync_attr_zweckbestimmung('zweckbestimmung'); + + create constraint trigger fp_gemeinbedarf_sync_attr_zweckbestimmung + after insert or update of allgemein on fp_zweckbestimmung_gemeinbedarf + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_gemeinbedarf_sync_attr_zweckbestimmung('gemeinbedarf_id'); + + create or replace function fp_gemeinbedarf_sync_attr_zweckbestimmung_on_delete() + returns trigger language plpgsql as $$ + begin + UPDATE fp_gemeinbedarf SET zweckbestimmung = array_remove(zweckbestimmung, OLD.allgemein) + WHERE id = OLD.gemeinbedarf_id; + RETURN NULL; + end $$; + + create constraint trigger fp_gemeinbedarf_sync_attr_zweckbestimmung_on_delete + after delete on fp_zweckbestimmung_gemeinbedarf + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_gemeinbedarf_sync_attr_zweckbestimmung_on_delete();; + +CREATE TABLE fp_zweckbestimmung_sport ( + id UUID NOT NULL, + allgemein xp_zweckbestimmungspielsportanlage, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + sportanlage_id UUID, + FOREIGN KEY(sportanlage_id) REFERENCES fp_spiel_sportanlage (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + ALTER TABLE fp_spiel_sportanlage ADD COLUMN "zugunstenVon" VARCHAR; + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM fp_spiel_sportanlage g + ) + INSERT INTO fp_zweckbestimmung_sport (id, allgemein, sportanlage_id) + SELECT * FROM upd; + + create or replace function fp_sport_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE fp_zweckbestimmung_sport + SET allgemein = NEW.zweckbestimmung + WHERE sportanlage_id = NEW.id; + ELSE + UPDATE fp_spiel_sportanlage SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.sportanlage_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger fp_sport_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on fp_spiel_sportanlage + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_sport_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger fp_sport_sync_attr_zweckbestimmung + after insert or update of allgemein on fp_zweckbestimmung_sport + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_sport_sync_attr_zweckbestimmung('sportanlage_id');; + +CREATE TABLE fp_zweckbestimmung_gruen ( + id UUID NOT NULL, + allgemein xp_zweckbestimmunggruen, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + gruenflaeche_id UUID, + FOREIGN KEY(gruenflaeche_id) REFERENCES fp_gruen (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + ALTER TABLE fp_gruen ADD COLUMN "zugunstenVon" VARCHAR; + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM fp_gruen g + ) + INSERT INTO fp_zweckbestimmung_gruen (id, allgemein, gruenflaeche_id) + SELECT * FROM upd; + + create or replace function fp_gruen_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE fp_zweckbestimmung_gruen + SET allgemein = NEW.zweckbestimmung + WHERE gruenflaeche_id = NEW.id; + ELSE + UPDATE fp_gruen SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.gruenflaeche_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger fp_gruen_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on fp_gruen + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_gruen_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger fp_gruen_sync_attr_zweckbestimmung + after insert or update of allgemein on fp_zweckbestimmung_gruen + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_gruen_sync_attr_zweckbestimmung('gruenflaeche_id');; + +CREATE TABLE fp_zweckbestimmung_landwirtschaft ( + id UUID NOT NULL, + allgemein xp_zweckbestimmunglandwirtschaft, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + landwirtschaft_id UUID, + FOREIGN KEY(landwirtschaft_id) REFERENCES fp_landwirtschaft (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM fp_landwirtschaft g + ) + INSERT INTO fp_zweckbestimmung_landwirtschaft (id, allgemein, landwirtschaft_id) + SELECT * FROM upd; + + create or replace function fp_landwirtschaft_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE fp_zweckbestimmung_landwirtschaft + SET allgemein = NEW.zweckbestimmung + WHERE landwirtschaft_id = NEW.id; + ELSE + UPDATE fp_landwirtschaft SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.landwirtschaft_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger fp_landwirtschaft_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on fp_landwirtschaft + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_landwirtschaft_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger fp_landwirtschaft_sync_attr_zweckbestimmung + after insert or update of allgemein on fp_zweckbestimmung_landwirtschaft + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_landwirtschaft_sync_attr_zweckbestimmung('landwirtschaft_id');; + +CREATE TABLE fp_zweckbestimmung_wald ( + id UUID NOT NULL, + allgemein xp_zweckbestimmungwald, + "textlicheErgaenzung" VARCHAR, + aufschrift VARCHAR, + waldflaeche_id UUID, + FOREIGN KEY(waldflaeche_id) REFERENCES fp_wald (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + WITH upd AS ( + SELECT uuid_generate_v4(), zweckbestimmung, id FROM fp_wald g + ) + INSERT INTO fp_zweckbestimmung_wald (id, allgemein, waldflaeche_id) + SELECT * FROM upd; + + create or replace function fp_wald_sync_attr_zweckbestimmung() + returns trigger language plpgsql as $$ + begin + IF TG_ARGV[0] = 'zweckbestimmmung' THEN + UPDATE fp_zweckbestimmung_wald + SET allgemein = NEW.zweckbestimmung + WHERE waldflaeche_id = NEW.id; + ELSE + UPDATE fp_wald SET zweckbestimmung = NEW.allgemein + WHERE id = NEW.waldflaeche_id; + END IF; + RETURN NEW; + end $$; + + create constraint trigger fp_wald_sync_attr_zweckbestimmung + after insert or update of zweckbestimmung on fp_wald + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_wald_sync_attr_zweckbestimmung('zweckbestimmmung'); + + create constraint trigger fp_wald_sync_attr_zweckbestimmung + after insert or update of allgemein on fp_zweckbestimmung_wald + DEFERRABLE + for each row when (pg_trigger_depth() = 0) + execute procedure fp_wald_sync_attr_zweckbestimmung('waldflaeche_id');; + +ALTER TABLE xp_plan_gemeinde ADD COLUMN lp_plan_id uuid REFERENCES lp_plan(id) ON DELETE CASCADE; + ALTER TABLE lp_plan ADD COLUMN plangeber_id uuid REFERENCES xp_plangeber(id); + + ALTER TABLE lp_plan ADD COLUMN "auslegungsStartDatum" DATE[]; + ALTER TABLE lp_plan ADD COLUMN "auslegungsEndDatum" DATE[]; + ALTER TABLE lp_plan ADD COLUMN "tOeBbeteiligungsStartDatum" DATE[]; + ALTER TABLE lp_plan ADD COLUMN "tOeBbeteiligungsEndDatum" DATE[]; + ALTER TABLE lp_plan ADD COLUMN "oeffentlichkeitsBetStartDatum" DATE[]; + ALTER TABLE lp_plan ADD COLUMN "oeffentlichkeitsBetEndDatum" DATE[]; + + ALTER TABLE lp_plan ADD COLUMN "veroeffentlichungsDatum" DATE; + + ALTER TABLE lp_plan ADD COLUMN "sonstVerfahrensText" VARCHAR; + ALTER TABLE lp_plan ADD COLUMN "startBedingungen" VARCHAR; + ALTER TABLE lp_plan ADD COLUMN "endeBedingungen" VARCHAR;; + +UPDATE alembic_version SET version_num='fb27f7a59e17' WHERE alembic_version.version_num = 'b93ec8372df6'; + +-- Running upgrade fb27f7a59e17 -> 8cdbef1a55d6 + +ALTER TABLE bp_plan ADD COLUMN "versionBauNVO_id" uuid REFERENCES xp_gesetzliche_grundlage(id); + ALTER TABLE bp_plan ADD COLUMN "versionBauGB_id" uuid REFERENCES xp_gesetzliche_grundlage(id); + ALTER TABLE bp_plan ADD COLUMN "versionSonstRechtsgrundlage_id" uuid REFERENCES xp_gesetzliche_grundlage(id);; + +ALTER TYPE xp_zweckbestimmunggemeinbedarf ADD VALUE 'SonstigeInfrastruktur'; + +ALTER TYPE xp_zweckbestimmunggemeinbedarf ADD VALUE 'SonstigeSicherheitOrdnung'; + +UPDATE alembic_version SET version_num='8cdbef1a55d6' WHERE alembic_version.version_num = 'fb27f7a59e17'; + +-- Running upgrade 8cdbef1a55d6 -> ce95b86bc010 + +CREATE TABLE codelist ( + id UUID NOT NULL, + name VARCHAR, + uri VARCHAR, + description VARCHAR, + + PRIMARY KEY (id) + ); + + CREATE TABLE codelist_values ( + id UUID NOT NULL, + value VARCHAR, + uri VARCHAR, + definition VARCHAR, + type VARCHAR, + + "codelist_id" UUID, + FOREIGN KEY("codelist_id") REFERENCES codelist (id) ON DELETE CASCADE, + PRIMARY KEY (id) + ); + + ALTER TABLE bp_plan ADD COLUMN "sonstPlanArt_id" uuid REFERENCES codelist_values(id); + ALTER TABLE bp_plan ADD COLUMN "status_id" uuid REFERENCES codelist_values(id); + ALTER TABLE bp_baugebiet ADD COLUMN "detaillierteArtDerBaulNutzung_id" uuid REFERENCES codelist_values(id); + ALTER TABLE bp_dachgestaltung ADD COLUMN "detaillierteDachform_id" uuid REFERENCES codelist_values(id); + + ALTER TABLE fp_plan ADD COLUMN "sonstPlanArt_id" uuid REFERENCES codelist_values(id); + ALTER TABLE fp_plan ADD COLUMN "status_id" uuid REFERENCES codelist_values(id); + ALTER TABLE fp_baugebiet ADD COLUMN "detaillierteArtDerBaulNutzung_id" uuid REFERENCES codelist_values(id); + + ALTER TABLE so_schienenverkehr ADD COLUMN "detailArtDerFestlegung_id" uuid REFERENCES codelist_values(id);; + ALTER TABLE so_denkmalschutz ADD COLUMN "detailArtDerFestlegung_id" uuid REFERENCES codelist_values(id); + +CREATE TABLE assoc_detail_sondernutzung ( + "codelist_id" UUID, + "codelist_user_id" UUID, + + FOREIGN KEY("codelist_user_id") REFERENCES bp_komplexe_sondernutzung (id) ON DELETE CASCADE, + FOREIGN KEY("codelist_id") REFERENCES codelist_values (id), + PRIMARY KEY (codelist_id, codelist_user_id) + ); + + CREATE TABLE assoc_detail_zweckgruen ( + "codelist_id" UUID, + "codelist_user_id" UUID, + + FOREIGN KEY("codelist_user_id") REFERENCES bp_zweckbestimmung_gruen (id) ON DELETE CASCADE, + FOREIGN KEY("codelist_id") REFERENCES codelist_values (id), + PRIMARY KEY (codelist_id, codelist_user_id) + );; + +CREATE TYPE xp_arthoehenbezug AS ENUM ( + 'absolutNHN', + 'absolutNN', + 'absolutDHHN', + 'relativGelaendeoberkante', + 'relativGehwegOberkante', + 'relativBezugshoehe', + 'relativStrasse', + 'relativEFH' + ); + + CREATE TYPE xp_arthoehenbezugspunkt AS ENUM ( + 'TH', + 'FH', + 'OK', + 'LH', + 'SH', + 'EFH', + 'HBA', + 'UK', + 'GBH', + 'WH', + 'GOK' + ); + + CREATE TABLE xp_hoehenangabe ( + id UUID NOT NULL, + "abweichenderHoehenbezug" VARCHAR, + hoehenbezug xp_arthoehenbezug, + "abweichenderBezugspunkt" VARCHAR, + bezugspunkt xp_arthoehenbezugspunkt, + "hMin" FLOAT, + "hMax" FLOAT, + "hZwingend" FLOAT, + "h" FLOAT, + + "xp_objekt_id" UUID, + "dachgestaltung_id" UUID, + FOREIGN KEY("xp_objekt_id") REFERENCES xp_objekt (id) ON DELETE CASCADE, + FOREIGN KEY("dachgestaltung_id") REFERENCES bp_dachgestaltung (id) ON DELETE CASCADE, + PRIMARY KEY (id) + );; + +CREATE TYPE bp_abgrenzungentypen AS ENUM ( + 'Nutzungsartengrenze', + 'UnterschiedlicheHoehen', + 'SonstigeAbgrenzung' + ); + + CREATE TABLE bp_nutzungsgrenze ( + id UUID NOT NULL, + typ bp_abgrenzungentypen, + + PRIMARY KEY (id) + );; + +CREATE TYPE bp_bereichohneeinausfahrttypen AS ENUM ( + 'KeineEinfahrt', + 'KeineAusfahrt', + 'KeineEinAusfahrt' + ); + + CREATE TABLE bp_keine_ein_ausfahrt ( + id UUID NOT NULL, + typ bp_bereichohneeinausfahrttypen, + + PRIMARY KEY (id) + );; + +CREATE TYPE bp_einfahrttypen AS ENUM ( + 'Einfahrt', + 'Ausfahrt', + 'EinAusfahrt' + ); + + CREATE TABLE bp_einfahrtpunkt ( + id UUID NOT NULL, + typ bp_einfahrttypen, + + PRIMARY KEY (id) + );; + +UPDATE alembic_version SET version_num='ce95b86bc010' WHERE alembic_version.version_num = '8cdbef1a55d6'; + +COMMIT; + diff --git a/src/SAGisXPlanung/ext/__init__.py b/src/SAGisXPlanung/ext/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/ext/roman.py b/src/SAGisXPlanung/ext/roman.py new file mode 100644 index 0000000..fd0c56d --- /dev/null +++ b/src/SAGisXPlanung/ext/roman.py @@ -0,0 +1,23 @@ +ROMAN = [ + (1000, "M"), + (900, "CM"), + (500, "D"), + (400, "CD"), + (100, "C"), + (90, "XC"), + (50, "L"), + (40, "XL"), + (10, "X"), + (9, "IX"), + (5, "V"), + (4, "IV"), + (1, "I"), +] + + +def to_roman(number: int): + result = "" + for (arabic, roman) in ROMAN: + (factor, number) = divmod(number, arabic) + result += roman * factor + return result diff --git a/src/SAGisXPlanung/ext/spinner.py b/src/SAGisXPlanung/ext/spinner.py new file mode 100644 index 0000000..a95bcce --- /dev/null +++ b/src/SAGisXPlanung/ext/spinner.py @@ -0,0 +1,238 @@ +""" +The MIT License (MIT) + +Copyright (c) 2012-2014 Alexander Turkin +Copyright (c) 2014 William Hallatt +Copyright (c) 2015 Jacob Dawid +Copyright (c) 2016 Luca Weiss +Copyright (c) 2017 fbjorn +Copyright (c) 2022 Jakob Wilhelm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import math + +from qgis.PyQt.QtCore import QRect, QTimer, Qt +from qgis.PyQt.QtGui import QColor, QPainter, QPaintEvent +from qgis.PyQt.QtWidgets import QWidget + + +class WaitingSpinner(QWidget): + + def __init__(self, parent, centerOnParent=True, disableParentWhenSpinning=False, + modality=Qt.NonModal, roundness=100., opacity=None, fade=80., lines=20, + line_length=10, line_width=2, radius=10, speed=math.pi / 2, color=(0, 0, 0)): + super().__init__(parent) + + self._centerOnParent = centerOnParent + self._disableParentWhenSpinning = disableParentWhenSpinning + + self._color = QColor(*color) + self._roundness = roundness + self._minimumTrailOpacity = math.pi + self._trailFadePercentage = fade + self._revolutionsPerSecond = speed + self._numberOfLines = lines + self._lineLength = line_length + self._lineWidth = line_width + self._innerRadius = radius + self._currentCounter = 0 + self._isSpinning = False + + self._timer = QTimer(self) + self._timer.timeout.connect(self.rotate) + self.updateSize() + self.updateTimer() + self.hide() + + self.setWindowModality(modality) + self.setAttribute(Qt.WA_TranslucentBackground) + + def paintEvent(self, e: QPaintEvent): + self.updatePosition() + painter = QPainter(self) + painter.fillRect(self.rect(), Qt.transparent) + painter.setRenderHint(QPainter.Antialiasing, True) + + if self._currentCounter >= self._numberOfLines: + self._currentCounter = 0 + + painter.setPen(Qt.NoPen) + for i in range(self._numberOfLines): + painter.save() + painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength) + rotateAngle = float(360 * i) / float(self._numberOfLines) + painter.rotate(rotateAngle) + painter.translate(self._innerRadius, 0) + distance = self.lineCountDistanceFromPrimary(i, self._currentCounter, self._numberOfLines) + color = self.currentLineColor( + distance, + self._numberOfLines, + self._trailFadePercentage, + self._minimumTrailOpacity, + self._color + ) + painter.setBrush(color) + painter.drawRoundedRect( + QRect(0, int(self._lineWidth / 2), self._lineLength, self._lineWidth), + self._roundness, + self._roundness, + Qt.RelativeSize + ) + painter.restore() + + def start(self): + self.updatePosition() + self._isSpinning = True + self.show() + + if self.parentWidget and self._disableParentWhenSpinning: + self.parentWidget().setEnabled(False) + + if not self._timer.isActive(): + self._timer.start() + self._currentCounter = 0 + + def stop(self): + self._isSpinning = False + self.hide() + + if self.parentWidget() and self._disableParentWhenSpinning: + self.parentWidget().setEnabled(True) + + if self._timer.isActive(): + self._timer.stop() + self._currentCounter = 0 + + def setNumberOfLines(self, lines): + self._numberOfLines = lines + self._currentCounter = 0 + self.updateTimer() + + def setLineLength(self, length): + self._lineLength = length + self.updateSize() + + def setLineWidth(self, width): + self._lineWidth = width + self.updateSize() + + def setInnerRadius(self, radius): + self._innerRadius = radius + self.updateSize() + + @property + def color(self): + return self._color + + @property + def roundness(self): + return self._roundness + + @property + def minimumTrailOpacity(self): + return self._minimumTrailOpacity + + @property + def trailFadePercentage(self): + return self._trailFadePercentage + + @property + def revolutionsPersSecond(self): + return self._revolutionsPerSecond + + @property + def numberOfLines(self): + return self._numberOfLines + + @property + def lineLength(self): + return self._lineLength + + @property + def lineWidth(self): + return self._lineWidth + + @property + def innerRadius(self): + return self._innerRadius + + @property + def isSpinning(self): + return self._isSpinning + + def setRoundness(self, roundness): + self._roundness = max(0.0, min(100.0, roundness)) + + def setColor(self, color=Qt.black): + self._color = QColor(color) + + def setRevolutionsPerSecond(self, revolutionsPerSecond): + self._revolutionsPerSecond = revolutionsPerSecond + self.updateTimer() + + def setTrailFadePercentage(self, trail): + self._trailFadePercentage = trail + + def setMinimumTrailOpacity(self, minimumTrailOpacity): + self._minimumTrailOpacity = minimumTrailOpacity + + def rotate(self): + self._currentCounter += 1 + if self._currentCounter >= self._numberOfLines: + self._currentCounter = 0 + self.update() + + def updateSize(self): + size = (self._innerRadius + self._lineLength) * 2 + self.setFixedSize(size, size) + + def updateTimer(self): + self._timer.setInterval(int(1000 / (self._numberOfLines * self._revolutionsPerSecond))) + + def updatePosition(self): + if self.parentWidget() and self._centerOnParent: + self.move( + int(self.parentWidget().width() / 2) - int(self.width() / 2), + int(self.parentWidget().height() / 2) - int(self.height() / 2) + ) + + def lineCountDistanceFromPrimary(self, current, primary, totalNrOfLines): + distance = primary - current + if distance < 0: + distance += totalNrOfLines + return distance + + def currentLineColor(self, countDistance, totalNrOfLines, trailFadePerc, minOpacity, colorinput): + color = QColor(colorinput) + if countDistance == 0: + return color + minAlphaF = minOpacity / 100.0 + distanceThreshold = int(math.ceil((totalNrOfLines - 1) * trailFadePerc / 100.0)) + if countDistance > distanceThreshold: + color.setAlphaF(minAlphaF) + else: + alphaDiff = color.alphaF() - minAlphaF + gradient = alphaDiff / float(distanceThreshold + 1) + resultAlpha = color.alphaF() - gradient * countDistance + # If alpha is out of bounds, clip it. + resultAlpha = min(1.0, max(0.0, resultAlpha)) + color.setAlphaF(resultAlpha) + return color diff --git a/src/SAGisXPlanung/gui/XPCreatePlanDialog.py b/src/SAGisXPlanung/gui/XPCreatePlanDialog.py new file mode 100644 index 0000000..acf2f47 --- /dev/null +++ b/src/SAGisXPlanung/gui/XPCreatePlanDialog.py @@ -0,0 +1,66 @@ +import asyncio +import logging +import os + +import qasync +from qgis.PyQt import QtGui, QtCore, QtWidgets, uic +from qgis.core import Qgis + +from SAGisXPlanung.gui.widgets.QXPlanTabWidget import QXPlanTabWidget +from SAGisXPlanung.utils import save_to_db + +FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), '../ui/XPlanung_create.ui')) +logger = logging.getLogger(__name__) + + +class XPCreatePlanDialog(QtWidgets.QDialog, FORM_CLASS): + + contentSaved = QtCore.pyqtSignal() + + def __init__(self, iface, cls_type, parent_type=None, parent=None): + super(XPCreatePlanDialog, self).__init__(parent=iface.mainWindow()) + self.setupUi(self) + self.class_type = cls_type + self.parent_type = parent_type + + self.setWindowTitle(f"Neues {self.class_type.__name__}-Objekt anlegen") + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + + self.buttons_hbox = QtWidgets.QHBoxLayout() + self.b_create = QtWidgets.QPushButton(f"{self.class_type.__name__} erstellen") + self.b_create.clicked.connect(self.createObject) + self.b_cancel = QtWidgets.QPushButton("Abbrechen") + self.b_cancel.clicked.connect(lambda: self.reject()) + self.buttons_hbox.addWidget(self.b_create) + self.buttons_hbox.addWidget(self.b_cancel) + self.buttons_hbox.setContentsMargins(0, 10, 0, 10) + self.vbox.addLayout(self.buttons_hbox) + + self.content = self.class_type() + self.iface = iface + + self.tabs = QXPlanTabWidget(self.class_type, parent_type=self.parent_type) + self.vbox.insertWidget(0, self.tabs) + + @qasync.asyncSlot() + async def createObject(self): + try: + QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor)) + + self.content = self.tabs.populateContent() + if not self.content: + return + + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, save_to_db, self.content) + + self.iface.messageBar().pushMessage("XPlanung", "Datensatz wurde gespeichert", level=Qgis.Success) + self.contentSaved.emit() + self.accept() + + except Exception as e: + logger.exception(f"error {e}") + self.iface.messageBar().pushMessage("XPlanung Fehler", "Datensatz konnte nicht gespeichert werden!", + str(e), level=Qgis.Critical) + finally: + QtWidgets.QApplication.restoreOverrideCursor() diff --git a/src/SAGisXPlanung/gui/XPEditAttributeDialog.py b/src/SAGisXPlanung/gui/XPEditAttributeDialog.py new file mode 100644 index 0000000..530f8b0 --- /dev/null +++ b/src/SAGisXPlanung/gui/XPEditAttributeDialog.py @@ -0,0 +1,88 @@ +import datetime +import logging +import os + +from qgis.PyQt import QtWidgets, uic, QtCore +from sqlalchemy import Date + +from SAGisXPlanung.config import xplan_tooltip +from SAGisXPlanung.gui.widgets.QXPlanInputElement import QXPlanInputElement, QFileInput + +FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), '../ui/XPlanung_edit_attribute.ui')) +logger = logging.getLogger(__name__) + + +class XPEditAttributeDialog(QtWidgets.QDialog, FORM_CLASS): + """ Dialog zum Ändern des Wertes eines XPlanung-Attributs. Sendet bei Änderung den angepassten Wert + über das attributeChanged-Signal """ + + attributeChanged = QtCore.pyqtSignal(object, object) # old_value, new_value + fileChanged = QtCore.pyqtSignal(object) + + def __init__(self, attribute_name, field_type, original_value, parent_xtype, parent=None): + """ + Parameters + ---------- + attribute_name: str + Name des XPlanung-Attributs + field_type: any + Spaltentyp, entweder aus sqlalchemy.types oder XPlanung.XPlan.types + original_value: + Wert des Attributs vor einer Änderung + parent_xtype: + XPlanung-Objektklasse + """ + super(XPEditAttributeDialog, self).__init__(parent) + + self.parent_xtype = parent_xtype + self.setupUi(self) + self.attribute_name = attribute_name + self.field_type = field_type + self.original_value = original_value + + self.discard_button = self.buttonBox.button(QtWidgets.QDialogButtonBox.Discard) + self.discard_button.clicked.connect(lambda s: self.setOriginalValue()) + + if isinstance(self.field_type, Date) and self.original_value: + self.original_value = datetime.datetime.strptime(self.original_value, "%d.%m.%Y") + + self.control: QXPlanInputElement = QXPlanInputElement.create(self.field_type, self) + self.setOriginalValue() + + self.hl1.addWidget(self.control) + + self.gbAttribute.setTitle(self.attribute_name) + self.gbAttribute.installEventFilter(self) + + if self.parent_xtype and self.attribute_name: + tooltip = xplan_tooltip(self.parent_xtype, self.attribute_name) + self.gbAttribute.setToolTip(tooltip) + + def setOriginalValue(self): + if self.original_value is not None: + self.control.setDefault(self.original_value) + + def accept(self): + if not self.control.validate_widget(False): + self.control.setInvalid(True) + return + + value = self.control.value() + if value != self.original_value: + self.attributeChanged.emit(self.original_value, value) + + if isinstance(self.control, QFileInput): + self.fileChanged.emit(self.control.file()) + + super(XPEditAttributeDialog, self).accept() + + def eventFilter(self, source, event): + if event.type() == QtCore.QEvent.ToolTip and isinstance(source, QtWidgets.QGroupBox): + options = QtWidgets.QStyleOptionGroupBox() + source.initStyleOption(options) + control = source.style().hitTestComplexControl(QtWidgets.QStyle.CC_GroupBox, options, event.pos()) + if control != QtWidgets.QStyle.SC_GroupBoxLabel and control != QtWidgets.QStyle.SC_GroupBoxCheckBox: + QtWidgets.QToolTip.hideText() + return True + return super().eventFilter(source, event) + diff --git a/src/SAGisXPlanung/gui/XPEditObjectDialog.py b/src/SAGisXPlanung/gui/XPEditObjectDialog.py new file mode 100644 index 0000000..9e3365f --- /dev/null +++ b/src/SAGisXPlanung/gui/XPEditObjectDialog.py @@ -0,0 +1,58 @@ +import logging + +from qgis.PyQt.QtWidgets import QVBoxLayout, QDialog, QDialogButtonBox + +from SAGisXPlanung import Session +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui.widgets.QXPlanTabWidget import QXPlanTabWidget + +logger = logging.getLogger(__name__) + + +class XPEditObjectDialog(QDialog): + """ Dialog zum Ändern von Attributen eines XPlanung-Objekts. Nur für einfache Objekte ohne Relationen!""" + + def __init__(self, xplan_item: XPlanungItem, parent=None): + """ + Parameters + ---------- + xplan_item: str + XPlanungItem des zu bearbeitenden Objekts + """ + super(XPEditObjectDialog, self).__init__(parent) + self.setWindowTitle(f'{xplan_item.xtype.__name__} bearbeiten') + + self.xplan_item = xplan_item + self.layout = QVBoxLayout(self) + self.buttons = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel) + self.buttons.accepted.connect(self.accept) + self.buttons.rejected.connect(self.reject) + + self.tab_widget = QXPlanTabWidget(self.xplan_item.xtype) + + # fill original values + with Session.begin() as session: + obj = session.query(self.xplan_item.xtype).get(self.xplan_item.xid) + for label, input_element in self.tab_widget.widget(0).fields.items(): + attribute_value = getattr(obj, label) + input_element.setDefault(attribute_value) + + self.layout.addWidget(self.tab_widget) + self.layout.addWidget(self.buttons) + self.setLayout(self.layout) + + self.adjustSize() + + def accept(self): + content = self.tab_widget.populateContent() + if not content: + return + + with Session.begin() as session: + obj = session.query(self.xplan_item.xtype).get(self.xplan_item.xid) + + # copy over attributes + for attr in obj.element_order(include_base=False, only_columns=True, export=True): + setattr(obj, attr, getattr(content, attr)) + + super(XPEditObjectDialog, self).accept() \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/XPEditPreFilledObjects.py b/src/SAGisXPlanung/gui/XPEditPreFilledObjects.py new file mode 100644 index 0000000..07ed760 --- /dev/null +++ b/src/SAGisXPlanung/gui/XPEditPreFilledObjects.py @@ -0,0 +1,203 @@ +import logging +import os +import uuid + +import qasync +from qgis.PyQt import uic +from qgis.PyQt.QtGui import QStandardItem, QStandardItemModel, QIcon +from qgis.PyQt.QtWidgets import QDialog, QMenu, QAction, QAbstractItemView +from qgis.PyQt.QtCore import Qt, pyqtSlot, QPoint, QSortFilterProxyModel, QModelIndex + +from SAGisXPlanung import Session, BASE_DIR +from SAGisXPlanung.config import export_version +from SAGisXPlanung.gui.widgets.QXPlanTabWidget import QXPlanTabWidget +from SAGisXPlanung.gui.style import HighlightRowProxyStyle, HighlightRowDelegate +from SAGisXPlanung.utils import PRE_FILLED_CLASSES, save_to_db_async, confirmObjectDeletion + +FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), '../ui/prefilled_object_edit.ui')) +logger = logging.getLogger(__name__) + +XID_ROLE = Qt.UserRole + 1 + + +class XPEditPreFilledObjectsDialog(QDialog, FORM_CLASS): + """ Dialog zum Ändern von XPlanung-Objekten, die als Vorauswahl beim Erstellen eines Plans zur Verfügung stehen.""" + + def __init__(self, parent=None): + super(XPEditPreFilledObjectsDialog, self).__init__(parent) + self.setupUi(self) + self.setAttribute(Qt.WA_DeleteOnClose) + + self.tab_widget = None + + self.model = QStandardItemModel() + self.proxy = QSortFilterProxyModel() + self.proxy.setSourceModel(self.model) + self.proxy.setSortCaseSensitivity(Qt.CaseInsensitive) + self.proxy.sort(0) + self.listView.setModel(self.proxy) + self.listView.setEnabled(False) + self.listView.setMouseTracking(True) + self.listView.setItemDelegate(HighlightRowDelegate()) + self.list_proxy_style = HighlightRowProxyStyle('Fusion') + self.list_proxy_style.setParent(self) + self.listView.setStyle(self.list_proxy_style) + + self.cbClass.currentIndexChanged.connect(self.onClassIndexChanged) + for cls in PRE_FILLED_CLASSES: + self.cbClass.addItem(cls.__name__, cls) + + self.listView.setEditTriggers(QAbstractItemView.NoEditTriggers) + self.listView.doubleClicked.connect(self.onDoubleClicked) + self.listView.setContextMenuPolicy(Qt.CustomContextMenu) + self.listView.customContextMenuRequested.connect(self.onContextMenuRequested) + + self.newObject.setStyleSheet(''' + QPushButton { + background: palette(window); + border: 1px solid #BFDBFE; + border-radius: 3px; + padding: 5px; + } + QPushButton:hover { + background-color: #93C5FD; + border: none; + }''') + + self.newObject.clicked.connect(self.onNewObjectClicked) + self.buttonBox.rejected.connect(self.resetLayout) + + @pyqtSlot(QPoint) + def onContextMenuRequested(self, pos: QPoint): + index = self.listView.indexAt(pos) + index = self.proxy.mapToSource(index) + if not index.isValid(): + return + + xplan_id = self.model.data(index, XID_ROLE) + if not xplan_id: + return + + menu = QMenu() + + edit_action = QAction(QIcon(':/images/themes/default/mActionMultiEdit.svg'), 'Bearbeiten') + edit_action.triggered.connect(lambda s, xid=xplan_id: self.onEditObject(xid)) + menu.addAction(edit_action) + + delete_action = QAction(QIcon(os.path.join(BASE_DIR, 'gui/resources/delete.svg')), 'Löschen') + delete_action.triggered.connect(lambda s, xid=xplan_id: self.onDeleteObject(xid)) + menu.addAction(delete_action) + + menu.exec_(self.listView.viewport().mapToGlobal(pos)) + + @pyqtSlot(int) + def onClassIndexChanged(self, index: int): + self.model.clear() + + cls = self.cbClass.itemData(index) + with Session.begin() as session: + objects = session.query(cls).all() + + self.listView.setEnabled(len(objects) > 0) + for o in objects: + item = QStandardItem(str(o)) + item.setData(str(o.id), XID_ROLE) + self.model.appendRow(item) + + @pyqtSlot(bool) + def onNewObjectClicked(self, checked: bool): + layout = self.pages.widget(1).layout() + + cls = self.cbClass.itemData(self.cbClass.currentIndex()) + self.tab_widget = QXPlanTabWidget(cls) + + layout.insertWidget(0, self.tab_widget) + + self.buttonBox.accepted.connect(self.newObjectAccepted) + self.pages.setCurrentIndex(1) + + @pyqtSlot() + def resetLayout(self): + layout = self.pages.widget(1).layout() + layout.removeWidget(self.tab_widget) + self.tab_widget.deleteLater() + self.tab_widget = None + self.buttonBox.accepted.disconnect() + self.pages.setCurrentIndex(0) + + @qasync.asyncSlot() + async def newObjectAccepted(self): + content = self.tab_widget.populateContent() + if not content: + return + content.id = uuid.uuid4() + + self.resetLayout() + + item = QStandardItem(str(content)) + item.setData(str(content.id), XID_ROLE) + self.model.appendRow(item) + + await save_to_db_async(content) + + def editAccepted(self, xid): + content = self.tab_widget.populateContent() + if not content: + return + content.id = uuid.uuid4() + + self.resetLayout() + + # update data in database + with Session.begin() as session: + obj = session.query(content.__class__).get(xid) + + # copy over attributes + for attr in obj.element_order(include_base=False, only_columns=True, export=True, + version=export_version()): + setattr(obj, attr, getattr(content, attr)) + + # update data in view + indices = self.model.match(self.model.index(0, 0), XID_ROLE, xid) + if indices: + self.model.setData(indices[0], str(content), Qt.DisplayRole) + + @pyqtSlot(QModelIndex) + def onDoubleClicked(self, index: QModelIndex): + index = self.proxy.mapToSource(index) + if not index.isValid(): + return + + self.onEditObject(index.data(XID_ROLE)) + + def onEditObject(self, xid): + layout = self.pages.widget(1).layout() + + cls = self.cbClass.itemData(self.cbClass.currentIndex()) + self.tab_widget = QXPlanTabWidget(cls) + + layout.insertWidget(0, self.tab_widget) + + with Session.begin() as session: + obj = session.query(cls).get(xid) + for label, input_element in self.tab_widget.widget(0).fields.items(): + attribute_value = getattr(obj, label) + input_element.setDefault(attribute_value) + + self.buttonBox.accepted.connect(lambda x=xid: self.editAccepted(x)) + self.pages.setCurrentIndex(1) + + def onDeleteObject(self, xid): + cls = self.cbClass.itemData(self.cbClass.currentIndex()) + with Session.begin() as session: + obj_from_db = session.query(cls).get(xid) + + if not confirmObjectDeletion(obj_from_db): + return + + session.delete(obj_from_db) + + # remove entry from view + indices = self.model.match(self.model.index(0, 0), XID_ROLE, xid) + if indices: + self.model.removeRow(indices[0].row()) diff --git a/src/SAGisXPlanung/gui/XPPlanDetailsDialog.py b/src/SAGisXPlanung/gui/XPPlanDetailsDialog.py new file mode 100644 index 0000000..5be4fe9 --- /dev/null +++ b/src/SAGisXPlanung/gui/XPPlanDetailsDialog.py @@ -0,0 +1,905 @@ +import asyncio +import functools +import inspect +import itertools +import logging +import os +import tempfile +import uuid +from operator import attrgetter +from typing import List, Tuple, Union + +import qasync +import yaml + +from qgis.PyQt import QtWidgets, QtGui +from qgis.PyQt.QtWidgets import QTreeWidgetItem, QAbstractItemView +from qgis.PyQt.QtCore import Qt, pyqtSignal, pyqtSlot, QEvent, QModelIndex, QSettings +from qgis.gui import QgsDockWidget +from qgis.core import (QgsRasterLayer, QgsProject, QgsGeometry, QgsVectorLayer, + QgsLayerTreeGroup, QgsLayerTreeLayer, Qgis, QgsAnnotationLayer) +from qgis.utils import iface +from sqlalchemy import select, exists +from sqlalchemy.orm import lazyload, load_only, selectinload, with_polymorphic + +from SAGisXPlanung import Session, BASE_DIR, SessionAsync, compile_ui_file, Base +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Plan +from SAGisXPlanung.BPlan.BP_Bebauung.feature_types import BP_BaugebietsTeilFlaeche +from SAGisXPlanung.FPlan.FP_Basisobjekte.feature_types import FP_Plan +from SAGisXPlanung.LPlan.LP_Basisobjekte.feature_types import LP_Plan +from SAGisXPlanung.RPlan.RP_Basisobjekte.feature_types import RP_Plan +from SAGisXPlanung.XPlan.XP_Praesentationsobjekte.feature_types import XP_Nutzungsschablone, \ + XP_AbstraktesPraesentationsobjekt +from SAGisXPlanung.XPlan.data_types import XP_Gemeinde +from SAGisXPlanung.XPlan.enums import XP_ExterneReferenzArt +from SAGisXPlanung.XPlan.feature_types import XP_Plan, XP_Bereich, XP_Objekt +from SAGisXPlanung.XPlan.mixins import PolygonGeometry, LineGeometry, MixedGeometry, PointGeometry +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.config import export_version, table_name_to_class +from SAGisXPlanung.ext.spinner import WaitingSpinner +from SAGisXPlanung.gui.actions import EnableBuldingTemplateAction, EditBuildingTemplateAction +from SAGisXPlanung.gui.commands import ObjectsDeletedCommand, XPUndoStack, AttributeChangedCommand +from SAGisXPlanung.gui.style import SVGButtonEventFilter, load_svg +from SAGisXPlanung.gui.widgets import QParishEdit +from SAGisXPlanung.gui.widgets.QAttributeEdit import QAttributeEdit +from SAGisXPlanung.gui.widgets.QCustomTreeWidgetItems import (QGeometryPolygonTreeWidgetItem, + QGeometryValidationTreeWidgetItem, + QGeometryDuplicateVerticesTreeWidgetItem, + QGeometryInvalidVerticesTreeWidgetItem, + GeometryIntersectionType) +from SAGisXPlanung.gui.widgets.QExplorerView import ClassNode, XID_ROLE +from SAGisXPlanung.gui.style.styles import TagStyledDelegate, HighlightRowProxyStyle +from SAGisXPlanung.gui.widgets.QXPlanTabWidget import QXPlanTabWidget +from SAGisXPlanung.utils import createXPlanungIndicators, OBJECT_BASE_TYPES, full_version_required_warning + +uifile = os.path.join(os.path.dirname(__file__), '../ui/XPlanung_plan_details.ui') +FORM_CLASS = compile_ui_file(uifile) + +logger = logging.getLogger(__name__) + + +class XPPlanDetailsDialog(QgsDockWidget, FORM_CLASS): + """ Dialog zum konfigurieren von vollständig vektoriell zu erfassenden Planinhalten """ + + planDeleted = pyqtSignal() + nameChanged = pyqtSignal(str, str) # xid, new plan name + + def __init__(self, parent=None): + super(XPPlanDetailsDialog, self).__init__(parent) + + self.plan_xid = None + self.plan_type = None + self.setupUi(self) + self.setAllowedAreas(self.allowedAreas() | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) + self.setObjectName('xplanung-details') + + self.deleteIcon = QtGui.QIcon(os.path.abspath(os.path.join(os.path.dirname(__file__), 'resources/delete.svg'))) + self.bMap.setIcon(QtGui.QIcon(os.path.abspath(os.path.join(os.path.dirname(__file__), 'resources/map.svg')))) + + self.bMap.clicked.connect(lambda state: reloadPlan(self.plan_xid)) + self.bDelete.setIcon(self.deleteIcon) + self.bDelete.clicked.connect(self.deletePlanContent) + self.bEdit.clicked.connect(self.showAttributesPage) + # self.bEdit.setDisabled(True) + self.bPrev.clicked.connect(self.prevPage) + self.bSave = self.bActions.button(QtWidgets.QDialogButtonBox.Save) + self.bSave.setVisible(False) + + self.bEditMain.setIcon(QtGui.QIcon(os.path.join(BASE_DIR, 'gui/resources/edit.svg'))) + self.bEditMain.clicked.connect(self.onEditMainClicked) + + self.bSortHierarchy.setIcon(QtGui.QIcon(os.path.join(BASE_DIR, 'gui/resources/sort_hierarchy.svg'))) + self.bSortCategory.setIcon(QtGui.QIcon(os.path.join(BASE_DIR, 'gui/resources/sort_category.svg'))) + self.bSortName.setIcon(QtGui.QIcon(os.path.join(BASE_DIR, 'gui/resources/sort_alpha.svg'))) + self.sortButtons.setId(self.bSortCategory, 2) + self.sortButtons.setId(self.bSortName, 1) + self.sortButtons.setId(self.bSortHierarchy, 0) + self.sortButtons.buttonClicked.connect(lambda: self.objectTree.sort(self.sortButtons.checkedId())) + + self.searchEdit.textChanged.connect(self.objectTree.filter) + + self.bValidate.clicked.connect(self.startValidation) + self.log.itemDoubleClicked.connect(self.onErrorDoubleClicked) + self.log.setContextMenuPolicy(Qt.CustomContextMenu) + self.log.customContextMenuRequested.connect(self.showGeometryValidationItemContextMenu) + self.log.setMouseTracking(True) + self.log.setItemDelegate(TagStyledDelegate()) + self.log_proxy_style = HighlightRowProxyStyle('Fusion') + self.log_proxy_style.setParent(self.log) + self.log.setStyle(self.log_proxy_style) + self.bFixAreas.clicked.connect(self.fillAreasWithoutUsage) + self.lFinished.setVisible(False) + self.reset_label.setVisible(False) + self.reset_label.mousePressEvent = self.onResetGeometryValidation + self.lErrorCount.setText('') + + self.parishEdit.hide() + self.parishEdit.parishChanged.connect(self.onParishChanged) + self.parish.parishEditRequested.connect(self.parishEdit.show) + + # self.objectTree.selectionModel().currentChanged.connect(lambda: self.bEdit.setDisabled(False)) + self.objectTree.setContextMenuPolicy(Qt.CustomContextMenu) + self.objectTree.customContextMenuRequested.connect(self.showObjectTreeContextMenu) + self.objectTree.doubleClicked.connect(self.showAttributesPage) + self.objectTree.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.stackedWidget.currentChanged.connect(self.updateButtons) + + self.validation_spinner = WaitingSpinner(self.log, disableParentWhenSpinning=True, radius=5, lines=20, + line_length=5, line_width=1, color=(0, 6, 128)) + self.init_spinner = WaitingSpinner(self, disableParentWhenSpinning=True, radius=5, lines=20, + line_length=5, line_width=1, color=(0, 6, 128)) + + self.undo_stack = XPUndoStack() + self.undo_stack.undoTextChanged.connect(lambda u: self.bUndo.setToolTip(f'Rückgängig: {u}' if u else '')) + self.undo_stack.redoTextChanged.connect(lambda r: self.bRedo.setToolTip(f'Vorwärts: {r}' if r else '')) + self.undo_stack.indexChanged.connect(self.onUndoStackChanged) + self.bUndo.setIcon(load_svg(os.path.join(BASE_DIR, 'gui/resources/undo.svg'), color='#1F2937')) + self.bRedo.setIcon(load_svg(os.path.join(BASE_DIR, 'gui/resources/redo.svg'), color='#1F2937')) + self.bUndo.setCursor(Qt.PointingHandCursor) + self.bRedo.setCursor(Qt.PointingHandCursor) + self.bUndo.setDisabled(True) + self.bRedo.setDisabled(True) + button_styling = ''' + QToolButton { + border: 0px; + cursor: pointer; + } + QToolButton:hover { + background: #E5E7EB; + } + ''' + self.bUndo.setStyleSheet(button_styling) + self.bRedo.setStyleSheet(button_styling) + self.button_highlight_filter = SVGButtonEventFilter(color='#1F2937', hover_color='black') + self.bUndo.installEventFilter(self.button_highlight_filter) + self.bRedo.installEventFilter(self.button_highlight_filter) + self.bUndo.clicked.connect(self.undo_stack.undo) + self.bRedo.clicked.connect(self.undo_stack.redo) + + def changeEvent(self, event: QEvent): + super(XPPlanDetailsDialog, self).changeEvent(event) + # widget dock status is changing + if event.type() == QEvent.ParentChange: + self.updateButtons() + + async def initPlanData(self, xid: str, keep_page=False): + self.init_spinner.start() + self.lFinished.setVisible(False) + self.reset_label.setVisible(False) + self.lErrorCount.setText('') + self.undo_stack.clear() + + self.log.clear() + self.objectTree.clear() + # self.attributeTree.clear() + if not keep_page: + self.stackedWidget.setCurrentIndex(0) + + with Session.begin() as session: + plan = session.query(XP_Plan).get(xid) + self.plan_xid = xid + self.plan_type = plan.__class__ + self.lTitle.setText(plan.name) + self.lPlanType.setText(self.plan_type.__name__) + if isinstance(plan, (BP_Plan, FP_Plan)): + self.parish.setText('; '.join(str(g) for g in plan.gemeinde)) + self.parish.setActive(True) + self.parishEdit.setup(plan.gemeinde) + elif isinstance(plan, (RP_Plan, LP_Plan)): + self.parish.setText(f'Bundesland: {plan.bundesland}') + self.parish.setActive(False) + + await self.constructExplorer(plan) + self.objectTree.expandAll() + self.init_spinner.stop() + + @pyqtSlot() + def updateButtons(self): + self.bPrev.setEnabled(self.stackedWidget.currentIndex() > 0) + self.bEdit.setEnabled(self.stackedWidget.currentIndex() == 0) + + self.bSave.setVisible(self.stackedWidget.currentIndex() == 2) + self.bUndo.setVisible(self.stackedWidget.currentIndex() != 2) + self.bRedo.setVisible(self.stackedWidget.currentIndex() != 2) + + @pyqtSlot() + def prevPage(self): + # self.attributeTree.clear() + widget = self.stackedWidget.currentWidget() + self.stackedWidget.removeWidget(widget) + widget.deleteLater() + self.stackedWidget.setCurrentIndex(0) + + @qasync.asyncSlot(int) + async def onUndoStackChanged(self, idx: int): + self.bUndo.setDisabled(idx == 0) + self.bRedo.setDisabled(self.undo_stack.count() == 0 or idx == self.undo_stack.count()) + + @pyqtSlot(bool) + def onEditMainClicked(self, clicked: bool): + with Session.begin() as session: + plan: XP_Plan = session.get(self.plan_type, self.plan_xid, [selectinload('*')]) + + edit_widget = plan.edit_widget() + self.insertWidgetIntoNewPage(edit_widget) + self.bSave.setVisible(True) + self.bSave.clicked.connect(self.onEditSaveClicked) + + @qasync.asyncSlot(dict) + async def onParishChanged(self, parish: dict): + self.parish.setText('; '.join(parish.keys())) + with Session.begin() as session: + parish_list = [] + for parish_name, parish_id in parish.items(): + xp_gemeinde = session.query(XP_Gemeinde).get(parish_id) + parish_list.append(xp_gemeinde) + + plan = session.query(XP_Plan).options(lazyload('*'), load_only('id')).get(self.plan_xid) + setattr(plan, 'gemeinde', parish_list) + + async def constructExplorer(self, plan): + xplan_item = XPlanungItem(xid=str(plan.id), xtype=plan.__class__) + node = ClassNode(xplan_item) + self.objectTree.model.addChild(node) + + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, self.iterateRelation, plan, node) + # self.iterateRelation(plan, node) + + async def addExplorerItem(self, parent_node: ClassNode, xplan_item: XPlanungItem, row=None): + node = ClassNode(xplan_item, new=True) + self.objectTree.model.addChild(node, parent_node, row) + + with Session.begin() as session: + obj = session.query(xplan_item.xtype).get(xplan_item.xid) + self.iterateRelation(obj, node) + + def showGeometryValidationItemContextMenu(self, point): + item = self.log.itemAt(point) + if not item: + return + + menu = QtWidgets.QMenu() + flash_action = QtWidgets.QAction(QtGui.QIcon(':/images/themes/default/mActionScaleHighlightFeature.svg'), + 'Geometriefehler auf Karte hervorheben') + flash_action.triggered.connect(self.highlightGeometryError) + menu.addAction(flash_action) + + menu.exec_(self.log.mapToGlobal(point)) + + def showObjectTreeContextMenu(self, point): + + selected_indices = self.objectTree.selectionModel().selectedIndexes() + if not selected_indices: + return + + menu = QtWidgets.QMenu() + menu.setToolTipsVisible(True) + + if len(selected_indices) > 1: + delete_action = QtWidgets.QAction(QtGui.QIcon(self.deleteIcon), 'Markierte Planinhalte löschen') + delete_action.triggered.connect(lambda state, indices=selected_indices: self.delete_indices(indices)) + menu.addAction(delete_action) + menu.exec_(self.objectTree.mapToGlobal(point)) + return + + item: ClassNode = selected_indices[0].model().itemAtIndex(selected_indices[0]) + if not item: + return + + if hasattr(item._data.xtype, 'geometry'): + flash_action = QtWidgets.QAction(QtGui.QIcon(':/images/themes/default/mActionScaleHighlightFeature.svg'), + 'Planinhalt auf Karte hervorheben') + flash_action.triggered.connect(self.highlightPlanContent) + menu.addAction(flash_action) + + if item.parent(): + delete_action = QtWidgets.QAction(QtGui.QIcon(self.deleteIcon), 'Planinhalt löschen') + delete_action.triggered.connect(lambda state, item_to_delete=item: self.onDeleteClick(item_to_delete)) + menu.addAction(delete_action) + + if item.xplanItem().xtype == BP_BaugebietsTeilFlaeche: + menu.addSeparator() + menu.addAction(EnableBuldingTemplateAction(item.xplanItem(), parent=menu)) + edit_template_action = EditBuildingTemplateAction(item.xplanItem(), parent=menu) + edit_template_action.editFormCreated.connect(self.insertWidgetIntoNewPage) + menu.addAction(edit_template_action) + + data_class_menu = QtWidgets.QMenu('Neues Datenobjekt hinzufügen') + for rel in item._data.xtype.relationships(): + if isinstance(rel[1].entity.class_, (PolygonGeometry, LineGeometry)): + continue + if next(iter(rel[1].remote_side)).primary_key or rel[1].secondary is not None: + continue + if hasattr(item._data.xtype, '__avoidRelation__') and rel[0] in item._data.xtype.__avoidRelation__: + continue + if not item.xplanItem().xtype.relation_fits_version(rel[0], export_version()): + continue + + action_name, _ = item.xplanItem().xtype.relation_prop_display(rel) + + data_class_action = QtWidgets.QAction(f'{action_name} ({rel[1].entity.class_.__name__})', self) + data_class_action.triggered.connect(lambda state, p_item=item, attr=rel[0], d_class=rel[1].entity.class_: + self.onCreateDataClass(p_item, d_class, attr)) + if not rel[1].uselist and item.childCount(): + for i in range(item.childCount()): + child_item = item.child(i) + with Session() as session: + col = next(iter(rel[1].remote_side)).description + ex = session.query(exists().where(getattr(rel[1].entity.class_, col) == item.id())).scalar() + if ex and child_item.xplanItem().xtype == rel[1].entity.class_: + data_class_action.setToolTip('Objekt existiert bereits!') + data_class_action.setEnabled(False) + data_class_menu.addAction(data_class_action) + + if not data_class_menu.isEmpty(): + menu.addSeparator() + menu.addMenu(data_class_menu) + + menu.exec_(self.objectTree.mapToGlobal(point)) + + def highlightGeometryError(self): + item: QGeometryValidationTreeWidgetItem = self.log.selectedItems()[0] + iface.mapCanvas().setExtent(item.extent()) + self.onErrorDoubleClicked(item, 0) # highlights error and refreshes canvas + + @pyqtSlot() + def highlightPlanContent(self): + item = self.objectTree.selectedItems()[0] + with Session.begin() as session: + plan_content = session.query(item._data.xtype).get(item._data.xid) + iface.mapCanvas().flashGeometries([plan_content.geometry()], plan_content.srs()) + + def onCreateDataClass(self, parent_item: ClassNode, data_class, attribute): + if issubclass(parent_item._data.xtype, XP_Objekt): + full_version_required_warning() + return + tab_widget = QXPlanTabWidget(data_class, parent_item._data.xtype) + self.bSave.clicked.connect(functools.partial(self.onSaveClicked, parent_item, attribute)) + self.stackedWidget.insertWidget(2, tab_widget) + self.stackedWidget.setCurrentIndex(2) + + @pyqtSlot(QtWidgets.QWidget) + def insertWidgetIntoNewPage(self, widget): + page_index = self.stackedWidget.currentIndex() + self.stackedWidget.insertWidget(page_index + 1, widget) + self.stackedWidget.setCurrentIndex(page_index + 1) + + @qasync.asyncSlot(bool) + async def onEditSaveClicked(self, checked: bool): + self.init_spinner.start() + + try: + async with SessionAsync.begin() as session: + tab_widget = self.stackedWidget.widget(self.stackedWidget.currentIndex()) + edited_object = tab_widget.populateContent() + if not edited_object: + return + edited_object.id = self.plan_xid + + plan = await session.merge(edited_object) + + await self.initPlanData(self.plan_xid, keep_page=True) + + self.prevPage() + self.bSave.clicked.disconnect() + + except Exception as e: + logger.exception(e) + finally: + self.init_spinner.stop() + + @qasync.asyncSlot(object, object, bool) + async def onSaveClicked(self, parent_item: ClassNode, attribute, checked: bool): + + self.init_spinner.start() + + try: + async with SessionAsync.begin() as session: + tab_widget = self.stackedWidget.widget(self.stackedWidget.currentIndex()) + data_obj = tab_widget.populateContent() + if not data_obj: + return + + data_obj.id = uuid.uuid4() + + stmt = select(parent_item._data.xtype).filter_by(id=parent_item._data.xid).options( + load_only(parent_item._data.xtype.id), + selectinload(getattr(parent_item._data.xtype, attribute)) + ) + result = await session.execute(stmt) + parent_obj = result.scalar_one() + + try: + getattr(parent_obj, attribute).append(data_obj) + except AttributeError: + setattr(parent_obj, attribute, data_obj) + await session.flush() + + data_obj = await session.get(data_obj.__class__, data_obj.id, + options=[selectinload('*')], populate_existing=True) + + xplan_item = XPlanungItem(xid=str(data_obj.id), xtype=data_obj.__class__) + node = ClassNode(xplan_item, new=True) + self.objectTree.model.addChild(node, parent_item) + self.iterateRelation(data_obj, node) + + self.prevPage() + self.bSave.clicked.disconnect() + + except Exception as e: + logger.exception(e) + finally: + self.init_spinner.stop() + + def iterateRelation(self, obj, root_node): + try: + for rel_item in obj.related(): + if isinstance(rel_item, XP_AbstraktesPraesentationsobjekt): + # don't show Nutzungsschablone in object tree, access via context menu instead + if rel_item.__class__ == XP_Nutzungsschablone: + continue + # only show PO objects as child node of their parent object (if they have any) + if rel_item.dientZurDarstellungVon_id and str(rel_item.dientZurDarstellungVon_id) != str( + obj.id): + continue + xplan_item = XPlanungItem( + xid=str(rel_item.id), + xtype=rel_item.__class__, + parent_xid=root_node.xplanItem().xid + ) + node = ClassNode(xplan_item, new=root_node.flag_new) + self.objectTree.model.addChild(node, root_node) + + self.iterateRelation(rel_item, node) + except Exception as e: + logger.exception(f"Exception on filling object explorer: {e}") + + @pyqtSlot() + def showAttributesPage(self): + """ Zeigt ein QTreeWidget mit den Attributen und Werten des aktuell im Objektbaum gewählten Objekts an """ + item = self.objectTree.selectedItems()[0] + attribute_config = yaml.safe_load(QSettings().value(f"plugins/xplanung/attribute_config", '')) or {} + + with Session.begin() as session: + session.expire_on_commit = False + plan_content = session.query(item._data.xtype).get(item._data.xid) + + base_classes = [c for c in list(inspect.getmro(item._data.xtype)) if issubclass(c, Base)] + + def skip_column(): + for mro_member in base_classes: + if attr in attribute_config.get(mro_member.__name__, []): + return True + return False + + data = [] + for attr in item._data.xtype.element_order(only_columns=True, version=export_version()): + + if skip_column(): + continue + + # edge case where same named column exists in base class which should be used + if not item._data.xtype.attr_fits_version(attr, export_version()): + cls = next(c for c in reversed(base_classes) if hasattr(c, attr)) + stmt = select(cls.__table__.c[attr]).where(cls.__table__.c.id == item._data.xid) + value = session.execute(stmt).scalar_one() + else: + value = getattr(plan_content, attr) + data.append([attr, value]) + + if isinstance(plan_content, (PointGeometry, PolygonGeometry, LineGeometry, MixedGeometry)): + if hasattr(plan_content, 'geomType'): + geom_type = plan_content.geomType() + else: + geom_type = plan_content.__geometry_type__ + else: + geom_type = None + + xplanung_item = XPlanungItem(xid=item._data.xid, xtype=item._data.xtype, plan_xid=self.plan_xid, + geom_type=geom_type) + attribute_edit_widget = QAttributeEdit.create(xplanung_item, data, self) + attribute_edit_widget.nameChanged.connect(self.onPlanNameChanged) + + for undo_command in self.undo_stack.iterate(_type=AttributeChangedCommand): + if undo_command.xplan_item.xid == xplanung_item.xid: + new_index = attribute_edit_widget.model_index(undo_command.attribute) + undo_command.setModelIndex(new_index) + undo_command.signal_proxy.changeApplied.connect(attribute_edit_widget.onChangeApplied) + + self.insertWidgetIntoNewPage(attribute_edit_widget) + + @qasync.asyncSlot(ClassNode) + async def onDeleteReverted(self, node: ClassNode): + xplan_item = node.xplanItem() + parent_id = xplan_item.parent_xid or xplan_item.bereich_xid or xplan_item.plan_xid + + # find parent, to add object + model = self.objectTree.model + index_list = model.match(model.index(0, 0), XID_ROLE, parent_id, -1, Qt.MatchWildcard | Qt.MatchRecursive) + + if not index_list: + return + + await self.addExplorerItem(model.itemAtIndex(index_list[0]), xplan_item, row=node.row()) + + @qasync.asyncSlot(ClassNode) + async def onDeleteApplied(self, node: ClassNode): + # find node to delete + model = self.objectTree.model + index_list = model.match(model.index(0, 0), XID_ROLE, node.xplanItem().xid, -1, + Qt.MatchWildcard | Qt.MatchRecursive) + + if not index_list: + return + model.removeRows(index_list[0].row(), 1, index_list[0].parent()) + + def onDeleteClick(self, item: ClassNode): + command = ObjectsDeletedCommand([item], self) + command.signal_proxy.deleteReverted.connect(self.onDeleteReverted) + command.signal_proxy.deleteApplied.connect(self.onDeleteApplied) + self.undo_stack.push(command) + + def delete_indices(self, indices: List[QModelIndex]): + _to_delete = [] + items = [] + for index in indices: + item = index.model().itemAtIndex(index) + parent_xid = item.xplanItem().parent_xid + if any(d for d in _to_delete if parent_xid in d): + continue + _to_delete.append((item.xplanItem().xtype, item.xplanItem().xid)) + items.append(item) + + deleted, _ = self.deletePlanContent(delete_map=_to_delete) + if deleted: + for i in items: + self.objectTree.removeItem(i) + + @pyqtSlot(str) + def onPlanNameChanged(self, updated_name: str): + self.lTitle.setText(updated_name) + self.nameChanged.emit(self.plan_xid, updated_name) + + @pyqtSlot() + def deletePlanContent(self, + class_type=None, + uid=None, + delete_map: Union[None, List[Tuple[type, str]]] = None) -> Tuple[bool, List[object]]: + """ + Löscht einen oder mehrere Planinhalte aus der Datenbank. + """ + with Session.begin() as session: + session.expire_on_commit = False + + items_to_delete = [] + if delete_map is not None: + for cls, xid in delete_map: + items_to_delete.append(session.query(cls).get(xid)) + elif uid is None: + items_to_delete.append(session.query(XP_Plan).get(self.plan_xid)) + else: + items_to_delete.append(session.query(class_type).get(uid)) + + msg = QtWidgets.QMessageBox() + msg.setIcon(QtWidgets.QMessageBox.Warning) + msg.setWindowTitle("Löschvorgang bestätigen") + msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel) + + if len(items_to_delete) <= 1: + msg.setText(f"Wollen Sie den Planinhalt unwideruflich löschen? (id: {items_to_delete[0].id})") + else: + t = f"Wollen Sie die Planinhalte unwideruflich löschen?
      " + for item in items_to_delete: + t += f'
    • {item.__class__.__name__}: {item.id}
    • ' + t += '
    ' + msg.setText(t) + ret = msg.exec_() + if ret == QtWidgets.QMessageBox.Cancel: + return False, [] + + for d in items_to_delete: + session.delete(d) + + if uid is None and delete_map is None: + self.planDeleted.emit() + return True, items_to_delete + + @qasync.asyncSlot() + async def fillAreasWithoutUsage(self): + self.bFixAreas.setEnabled(False) + with Session.begin() as session: + plan: XP_Plan = session.query(XP_Plan).get(self.plan_xid) + loop = asyncio.get_running_loop() + xplan_items = await loop.run_in_executor(None, plan.enforceFlaechenschluss) + + for item in xplan_items: + m = self.objectTree.model + # find item + index_list = m.match(m.index(0, 0), XID_ROLE, item.xid, -1, Qt.MatchWildcard | Qt.MatchRecursive) + if index_list: + index_list[0].internalPointer().flag_new = True + m.dataChanged.emit(index_list[0], index_list[0]) + continue + + # else find parent and add new item + index_list = m.match(m.index(0, 0), XID_ROLE, item.parent_xid, -1, Qt.MatchWildcard | Qt.MatchRecursive) + if index_list: + await self.addExplorerItem(index_list[0], item, 0) + + self.bFixAreas.setEnabled(True) + iface.messageBar().pushMessage("XPlanung", "Bilden des Flaechenschluss abgeschlossen", level=Qgis.Info) + + @qasync.asyncSlot() + async def startValidation(self): + self.validation_spinner.start() + + self.bValidate.setEnabled(False) + self.lFinished.setVisible(False) + self.reset_label.setVisible(False) + self.lErrorCount.setText('') + self.log.clear() + + async with SessionAsync.begin() as session: + # highly optimized query to only load required columns and populate all required sub tables from the + # beginning so that no further sql is emitted + xp_objekt_polymorphic = with_polymorphic(XP_Objekt, "*") + attribute_tuples = [(attrgetter(f'{o_type.__name__}.position')(xp_objekt_polymorphic), + attrgetter(f'{o_type.__name__}.flaechenschluss')(xp_objekt_polymorphic)) + for o_type in OBJECT_BASE_TYPES] + attribute_list = itertools.chain(*attribute_tuples) + opts = [load_only('id', 'raeumlicherGeltungsbereich'), + selectinload('bereich').options( + load_only('id', 'geltungsbereich'), + selectinload(XP_Bereich.planinhalt.of_type(xp_objekt_polymorphic)).options( + load_only('id', *attribute_list) + ) + )] + # plan = session.query(self.plan_type).options(*opts).get(self.plan_xid) + plan = await session.get(self.plan_type, self.plan_xid, opts) + + # validation tasks are heavy cpu work, therefore run them in threadpool + # unfortunately ProcessPoolExecutor does not work inside QGIS -> can't use multiprocessing to side-step GIL + loop = asyncio.get_event_loop() + await loop.run_in_executor(None, self.validateFlaechenschluss, plan) + await loop.run_in_executor(None, self.validateUniqueVertices, plan) + + error_count = self.log.topLevelItemCount() + self.lFinished.setVisible(True) + self.lErrorCount.setText(f'{error_count} Fehler gefunden' if error_count else 'Keine Fehler gefunden') + if error_count: + self.reset_label.setVisible(True) + self.bValidate.setEnabled(True) + + self.validation_spinner.stop() + + def validateFlaechenschluss(self, plan): + """ + Validierung der Flächenschlussbedingung (d.h. die Vereinigung aller Planinhalte in Ebene 0 + stimmt mit der Fläche des Geltungsbereichs überein <=> es existieren kleine Überlappungen/Klaffung zwischen + den Flächen der Planinhalte und jeder Stützpunkt liegt auf mindestens einem anderen) + """ + self._validate_within_bounds(plan) + self._validate_overlaps(plan) + + # validate that the union of all plan contents is equal to the geltungsbereich + p = str(plan.__class__.__name__[:2]).lower() + with Session.begin() as session: + stmt = f""" + SELECT + ST_AsText((ST_dump(st_difference(xp_plan."raeumlicherGeltungsbereich", plan_contents.united))).geom) as wkt, xp_plan.id + FROM + ( + SELECT ST_union(a.position) as united, {p}_bereich."gehoertZuPlan_id" AS plan_id + FROM + ( + SELECT a.id, a.flaechenschluss, a.position FROM {p}_objekt a + UNION + SELECT so_a.id, so_a.flaechenschluss, so_a.position FROM so_objekt so_a + ) a, + xp_objekt xp_a, {p}_bereich + WHERE xp_a.id = a.id AND a.flaechenschluss = True AND xp_a."gehoertZuBereich_id" = {p}_bereich.id + GROUP BY {p}_bereich."gehoertZuPlan_id" + ) as plan_contents, + xp_plan + WHERE plan_contents.plan_id = xp_plan.id AND xp_plan.id = '{plan.id}'; + """ + res = session.execute(stmt) + for row in res: + QGeometryPolygonTreeWidgetItem(self.log, uid=str(row.id), cls_type=XP_Plan, + polygon=row.wkt, + intersection_type=GeometryIntersectionType.NotCovered) + + def validateUniqueVertices(self, plan: XP_Plan): + """ Startet die Untersuchung aller Stützpunkte von XPlanung-Geometrien auf Duplikate""" + self.checkUniqueVertices(plan) + for b in plan.bereich: + self.checkUniqueVertices(b) + for p in b.planinhalt: + self.checkUniqueVertices(p) + + def checkUniqueVertices(self, obj): + """ überprüft ein XPlanung-Objekt auf doppelte Stützpunkte und schreibt das Ergebnis in das Log-Fenster""" + # try if geometry is not empty + try: + geom_copy = QgsGeometry(obj.geometry()) + except AssertionError: + return + nodes_removed = geom_copy.removeDuplicateNodes() + if nodes_removed: + QGeometryDuplicateVerticesTreeWidgetItem(self.log, uid=str(obj.id), cls_type=obj.__class__, + polygon=geom_copy.asWkt()) + + def _validate_within_bounds(self, plan: XP_Plan): + """ validate if all geometries of plan contents are within the bounds of the plan """ + plan_geom = plan.geometry() + plan_geom_const = plan_geom.constGet() + for b in plan.bereich: + b_geom = b.geometry() + b_engine = QgsGeometry.createGeometryEngine(b_geom.constGet()) + b_engine.prepareGeometry() + # check that bereich is within bounds of plan + difference = b_engine.difference(plan_geom_const) + if not difference.isEmpty(): + QGeometryPolygonTreeWidgetItem(self.log, uid=str(b.id), cls_type=b.__class__, + polygon=difference.asWkt(), + intersection_type=GeometryIntersectionType.Plan) + + flaechenschluss_objekte = [p for p in b.planinhalt if p.flaechenschluss] + for fs_objekt in flaechenschluss_objekte: + geom = fs_objekt.geometry() + const_geom = geom.constGet() + fl_engine = QgsGeometry.createGeometryEngine(const_geom) + fl_engine.prepareGeometry() + + # check that geometries are within correct bounds of bereich + difference = fl_engine.difference(b_geom.constGet()) + if not difference.isEmpty(): + QGeometryPolygonTreeWidgetItem(self.log, uid=str(fs_objekt.id), cls_type=fs_objekt.__class__, + polygon=difference.asWkt(), + intersection_type=GeometryIntersectionType.Bereich) + + def _validate_overlaps(self, plan: XP_Plan): + """ validates if any of the plan contents overlap each other""" + p = str(plan.__class__.__name__[:2]).lower() + with Session.begin() as session: + stmt = f""" + SELECT ST_AsText(ST_CollectionExtract(ST_INTERSECTION(a.position, b.position))) AS wkt, + xp_a.id, xp_a.type, xp_plan.id + FROM {p}_objekt a, {p}_objekt b, xp_objekt xp_a, xp_objekt xp_b, xp_plan, {p}_bereich + WHERE + a.ID < b.ID AND + a.id = xp_a.id AND + b.id = xp_b.id AND + ST_IsValid(a.position) AND ST_IsValid(b.position) AND + a.flaechenschluss = True AND b.flaechenschluss = True AND + xp_plan.id = {p}_bereich."gehoertZuPlan_id" AND + {p}_bereich.id = xp_a."gehoertZuBereich_id" AND {p}_bereich.id = xp_b."gehoertZuBereich_id" AND + xp_plan.id = '{plan.id}' AND + ST_OVERLAPS(a.position, b.position); + """ + + res = session.execute(stmt) + for row in res: + QGeometryPolygonTreeWidgetItem(self.log, uid=str(row.id), cls_type=table_name_to_class(row.type), + polygon=row.wkt, + intersection_type=GeometryIntersectionType.Planinhalt) + + @pyqtSlot(QTreeWidgetItem, int) + def onErrorDoubleClicked(self, item: QGeometryValidationTreeWidgetItem, column): + for i in range(self.log.topLevelItemCount()): + self.log.topLevelItem(i).removeFromCanvas() + item.displayErrorOnCanvas() + iface.mapCanvas().refresh() + + def onResetGeometryValidation(self, event): + self.log.clear() + + self.lFinished.setVisible(False) + self.reset_label.setVisible(False) + self.lErrorCount.setText('') + + +def createRasterLayer(layer_name, file, group=None): + """ + Fügt dem aktuellen QGIS-Projekt ein neuen Rasterlayer hinzu + + Parameters + ---------- + layer_name: str + Name des Layers, wird im LayerTree angezeigt + file: bytes + Inhalt des Rasterbilds + group: QgsLayerTreeGroup, optional + Layer wird dieser Gruppe im LayerTree hinzugefügt + """ + + with tempfile.NamedTemporaryFile(delete=False) as tmp: + tmp.write(file) + + layer = QgsRasterLayer(tmp.name, layer_name) + if group: + QgsProject.instance().addMapLayer(layer, False) + group.addLayer(layer) + else: + QgsProject.instance().addMapLayer(layer) + + +def reloadPlan(plan_xid): + """ + Ein bereits auf der Karte gerenderter Plan wird neu geladen. Sollte der Plan noch nicht auf der Karte bestehen, + wird er erstmals geladen. + """ + layers = QgsProject.instance().layerTreeRoot().findGroups(recursive=True) + for group in layers: + if not isinstance(group, QgsLayerTreeGroup) or 'xplanung_id' not in group.customProperties(): + continue + + if group.customProperty('xplanung_id') == plan_xid: + displayPlanOnCanvas(plan_xid, layer_group=group) + return + + displayPlanOnCanvas(plan_xid) + + +def displayPlanOnCanvas(plan_xid, layer_group=None, existing_group=True): + """ + Fügt den aktuell gewählten Plan als Layer zur Karte hinzu + """ + iface.mainWindow().statusBar().showMessage('Planwerk wird geladen...') + with Session.begin() as session: + plan: XP_Plan = session.query(XP_Plan).get(plan_xid) + if plan is None: + raise Exception(f'plan with id {plan_xid} not found') + + root = QgsProject.instance().layerTreeRoot() + + if not layer_group: + existing_group = False + layer_group = root.insertGroup(0, plan.name) + layer_group.setCustomProperty('xplanung_id', str(plan.id)) + + xp_indicator, reload_indicator = createXPlanungIndicators() + reload_indicator.clicked.connect(lambda i, p=plan_xid: reloadPlan(p)) + + iface.layerTreeView().addIndicator(layer_group, xp_indicator) + iface.layerTreeView().addIndicator(layer_group, reload_indicator) + else: + for tree_layer in layer_group.findLayers(): # type: QgsLayerTreeLayer + map_layer = tree_layer.layer() + if isinstance(map_layer, QgsVectorLayer): + truncate_success = map_layer.dataProvider().truncate() + if not truncate_success: + logger.warning(f'Could not truncate features of vector layer {map_layer.name()}') + elif isinstance(map_layer, QgsAnnotationLayer): + map_layer.clear() + elif isinstance(map_layer, QgsRasterLayer): + QgsProject.instance().removeMapLayer(map_layer) + + plan.toCanvas(layer_group) + + for b in [b for b in plan.bereich]: # if b.geltungsbereich? only load if geltungsbereich has geom + for planinhalt in b.planinhalt: + planinhalt.toCanvas(layer_group, plan_xid=plan.id) + + # display "free" annotations which are not bound to a 'planinhalt' + for po in b.praesentationsobjekt: + if po.dientZurDarstellungVon_id: + continue + po.toCanvas(layer_group, plan_xid=plan.id) + + for simple_object in b.simple_geometry: + simple_object.toCanvas(layer_group, plan_xid=plan.id) + + if b.geltungsbereich: + b.toCanvas(layer_group, plan_xid=plan.id) + + for refScan in b.refScan: + if refScan.art == XP_ExterneReferenzArt.PlanMitGeoreferenz and refScan.file is not None: + createRasterLayer(refScan.referenzName, refScan.file, group=layer_group) + + for ext_ref in plan.externeReferenz: + if ext_ref.art == XP_ExterneReferenzArt.PlanMitGeoreferenz and ext_ref.file is not None: + createRasterLayer(ext_ref.referenzName, ext_ref.file, group=layer_group) + + iface.mainWindow().statusBar().showMessage('Planwerk auf der Karte geladen.') diff --git a/src/SAGisXPlanung/gui/XPlanGroupMenuProvider.py b/src/SAGisXPlanung/gui/XPlanGroupMenuProvider.py new file mode 100644 index 0000000..ab24540 --- /dev/null +++ b/src/SAGisXPlanung/gui/XPlanGroupMenuProvider.py @@ -0,0 +1,48 @@ +import logging + +from qgis.PyQt.QtWidgets import QMenu +from qgis.core import QgsVectorLayer, QgsRasterLayer +from qgis.gui import QgsLayerTreeViewMenuProvider +from qgis.utils import iface + +logger = logging.getLogger(__name__) + + +# TODO: https://gis.stackexchange.com/questions/382780/qgis-contextual-menu-overriding +class XPlanGroupMenuProvider(QgsLayerTreeViewMenuProvider): + + def __init__(self, view): + super(XPlanGroupMenuProvider, self).__init__() + self.view = view + self.defaultActions = view.defaultActions() + + def createContextMenu(self): + if not self.view.currentLayer(): + return None + # m = super().createContextMenu() # TODO: this call is the problem, see above link + m = QMenu() + m.addAction("Open layer properties", self.openLayerProperties) + m.addSeparator() + + if type(self.view.currentLayer()) == QgsVectorLayer: + m.addAction("Show Feature Count", self.featureCount) + m.addAction("Another vector-specific action", self.vectorAction) + elif type(self.view.currentLayer()) == QgsRasterLayer: + m.addAction("Zoom 100%", self.zoom100) + m.addAction("Another raster-specific action", self.rasterAction) + return m + + def openLayerProperties(self): + iface.showLayerProperties(self.view.currentLayer()) + + def featureCount(self): + self.defaultActions.actionShowFeatureCount().trigger() + + def vectorAction(self): + pass + + def zoom100(self): + iface.actionZoomActualSize().trigger() + + def rasterAction(self): + pass \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/XPlanungDialog.py b/src/SAGisXPlanung/gui/XPlanungDialog.py new file mode 100644 index 0000000..b53ab4a --- /dev/null +++ b/src/SAGisXPlanung/gui/XPlanungDialog.py @@ -0,0 +1,273 @@ +import asyncio +import logging +import os +from pathlib import Path + +import qasync + +from qgis.core import Qgis +from qgis.PyQt import QtWidgets +from qgis.PyQt.QtGui import QIcon, QCursor, QKeySequence +from qgis.PyQt.QtCore import Qt, pyqtSlot, QItemSelectionModel, QSettings +from qgis.PyQt.QtWidgets import QAbstractItemView, QApplication, QFileDialog +from qgis.gui import QgsDockWidget +from qgis.utils import iface + +from SAGisXPlanung import compile_ui_file +from SAGisXPlanung.ConverterTasks import export_plan, import_plan +from SAGisXPlanung.Tools.ContextMenuTool import ContextMenuTool +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui.widgets import QBuildingTemplateEdit +from SAGisXPlanung.gui.widgets.QExplorerView import XID_ROLE +from SAGisXPlanung.utils import CLASSES +from SAGisXPlanung.gui.XPCreatePlanDialog import XPCreatePlanDialog +from SAGisXPlanung.gui.XPPlanDetailsDialog import XPPlanDetailsDialog +# don't remove following dependency, it is needed for promoting a ComboBox to QPlanComboBox via qt designer +from SAGisXPlanung.gui.widgets.QPlanComboBox import QPlanComboBox + +uifile = os.path.join(os.path.dirname(__file__), '../ui/XPlanung_dialog_base.ui') +FORM_CLASS = compile_ui_file(uifile) + +logger = logging.getLogger(__name__) + + +class XPlanungDialog(QgsDockWidget, FORM_CLASS): + """ + Hauptdialog der Anwendung. + """ + + def __init__(self, parent=None): + super(XPlanungDialog, self).__init__(parent) + self.setupUi(self) + self.setAllowedAreas(self.allowedAreas() | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) + self.iface = iface + self.export_task = None + self.import_task = None + + self.bCreate.clicked.connect(lambda: self.showCreateForm()) + self.bExport.clicked.connect(lambda: self.export()) + self.bImport.clicked.connect(lambda: self.importGML()) + self.bInfo.setIcon(QIcon(os.path.abspath(os.path.join(os.path.dirname(__file__), 'resources/info.svg')))) + self.bInfo.clicked.connect(self.openDetails) + + self.identifyTool = ContextMenuTool(self.iface.mapCanvas(), self) + self.identifyTool.accessAttributesRequested.connect(self.showObjectAttributes) + self.identifyTool.highlightObjectTreeRequested.connect(self.selectTreeItem) + self.identifyTool.featureSaved.connect(self.onFeatureSaved) + self.identifyTool.editBuildingTemplateRequested.connect(self.showTemplateEdit) + self.bIdentify.setIcon(QIcon(':/images/themes/default/mActionIdentify.svg')) + self.bIdentify.clicked.connect(self.onIdentifyClicked) + self.bIdentify_shortcut = QtWidgets.QShortcut(QKeySequence(Qt.ALT | Qt.Key_Q), self) + self.bIdentify_shortcut.activated.connect(lambda: self.bIdentify.click()) + self.iface.mapCanvas().mapToolSet.connect(self.onMapToolChanged) + + self.details_dialog = XPPlanDetailsDialog(parent=iface.mainWindow()) + self.details_dialog.planDeleted.connect(self.cbPlaene.refresh) + self.details_dialog.nameChanged.connect(self.onPlanNameChanged) + + self.progress_widget.setVisible(False) + + self.opened.connect(self.onOpened) + self.closed.connect(self.onClosed) + self.cbPlaene.currentIndexChanged.connect(self.onIndexChanged) + + self.fwImportPath.fileChanged.connect(lambda file_path: self.bImport.setEnabled(bool(file_path))) + + def __del__(self): + try: + self.iface.mapCanvas().mapToolSet.disconnect(self.onMapToolChanged) + except TypeError: + pass + + def selectedPlan(self): + return self.cbPlaene.currentPlanId() + + @qasync.asyncSlot() + async def onOpened(self): + # TODO: maybe disconnect slot after first call + await self.onIndexChanged(self.cbPlaene.currentIndex()) + + def onClosed(self): + self.details_dialog.hide() + + @qasync.asyncSlot(int) + async def onIndexChanged(self, i): + self.bExport.setEnabled(False) + self.bInfo.setEnabled(False) + if i != -1: + xid = self.cbPlaene.currentPlanId() + await self.details_dialog.initPlanData(xid) + else: + self.details_dialog.hide() + + self.bInfo.setDisabled(i == -1) + self.bExport.setDisabled(i == -1) + + @qasync.asyncSlot(str, str) + async def onPlanNameChanged(self, xid: str, updated_name: str): + self.cbPlaene.setPlanName(xid, updated_name) + + def onIdentifyClicked(self, checked: bool): + if checked: + self.iface.mapCanvas().setMapTool(self.identifyTool) + elif self.iface.mapCanvas().mapTool() == self.identifyTool: + self.iface.mapCanvas().unsetMapTool(self.identifyTool) + + def onMapToolChanged(self, new_tool, old_tool): + if old_tool == self.identifyTool and self.bIdentify.isChecked(): + self.bIdentify.setChecked(False) + + def showCreateForm(self): + """ + Startet den Dialog zum Erstellen eines neuen Planinhalts aus dem augewählten Layer. + """ + + def _show_dialog(d): + d.finished.connect(lambda: self.cbPlaene.refresh()) + d.show() + + if self.rbBPlan.isChecked(): + _show_dialog(XPCreatePlanDialog(iface, CLASSES['BP_Plan'])) + elif self.rbFPlan.isChecked(): + _show_dialog(XPCreatePlanDialog(iface, CLASSES['FP_Plan'])) + elif self.rbRPlan.isChecked(): + _show_dialog(XPCreatePlanDialog(iface, CLASSES['RP_Plan'])) + elif self.rbLPlan.isChecked(): + _show_dialog(XPCreatePlanDialog(iface, CLASSES['LP_Plan'])) + else: + self.iface.messageBar().pushMessage("XPlanung Fehler", "Keine Planart ausgewählt", + level=Qgis.Warning) + + @qasync.asyncSlot() + async def export(self): + """ + Wandelt einen in der ComboBox gewählten Planinhalt in ein XPlanGML-Dokument um. + Nutzt die aktive PostgreSQL-Verbindung die über das XPlanung-Einstellungsmenü konfiguriert wurde. + """ + self.bExport.setEnabled(False) + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + self.bExport.repaint() + + try: + plan_name = self.cbPlaene.currentText().rpartition(' (')[0] + display_name = plan_name.replace("/", "-").replace('"', '\'') + out_file_format = "gml" if self.rbGML.isChecked() else "zip" + + qs = QSettings() + default_dir = qs.value('plugins/xplanung/last_export_dir', '') + + export_filename = QFileDialog.getSaveFileName(self, 'Speicherort auswählen', + directory=f'{default_dir}{display_name}.{out_file_format}', + filter=f'*.{out_file_format}') + + export_path = Path(export_filename[0]) + qs.setValue('plugins/xplanung/last_export_dir', f'{export_path.parent}\\') + + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, export_plan, out_file_format, export_filename[0], plan_name) + + iface.messageBar().pushMessage("XPlanung", f"Planwerk {plan_name} erfolgreich exportiert!", + level=Qgis.Success) + os.startfile(export_filename[0]) + except Exception as e: + logger.exception(e) + iface.messageBar().pushMessage("XPlanung Fehler", "XPlanGML-Dokument konnte nicht umgewandelt werden!", + str(e), level=Qgis.Critical) + + finally: + self.bExport.setEnabled(True) + QtWidgets.QApplication.restoreOverrideCursor() + + @qasync.asyncSlot() + async def importGML(self): + """ + Wandelt ein XPlanGML-Dokument in einen PostgreSQL-Datensatz um. + Nutzt die aktive PostgreSQL-Verbindung die über das XPLanung-Einstellungsmenü konfiguriert wurde. + """ + filepath = self.fwImportPath.filePath() + if not filepath: + self.iface.messageBar().pushMessage("XPlanung Fehler", "Kein Pfad zur XPlanGML-Datei angegeben", + level=Qgis.Critical) + return + + self.bImport.setEnabled(False) + QtWidgets.QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + self.bImport.repaint() + self.progress_widget.setVisible(True) + + try: + loop = asyncio.get_running_loop() + plan_name = await loop.run_in_executor(None, import_plan, filepath, self.import_progress) + + self.fwImportPath.setFilePath("") + self.cbPlaene.refresh() + iface.messageBar().pushMessage("XPlanung", f"Planwerk {plan_name} erfolgreich importiert!", + level=Qgis.Success) + + except Exception as e: + logger.exception(e) + iface.messageBar().pushMessage("XPlanung Fehler", "XPlanGML-Dokument konnte nicht importiert werden!", + str(e), level=Qgis.Critical) + + finally: + self.bImport.setEnabled(True) + QtWidgets.QApplication.restoreOverrideCursor() + self.progress_widget.setVisible(False) + + def import_progress(self, progress): + self.progress_label.setText(f'{progress[0]}/{progress[1]}') + + @pyqtSlot() + def openDetails(self): + self.details_dialog.show() + self.details_dialog.setUserVisible(True) + + @qasync.asyncSlot(XPlanungItem) + async def showObjectAttributes(self, xplan_item: XPlanungItem): + await self.selectTreeItem(xplan_item) + + # open attributes page + self.details_dialog.stackedWidget.setCurrentIndex(0) + self.details_dialog.showAttributesPage() + self.openDetails() + + @qasync.asyncSlot(XPlanungItem) + async def selectTreeItem(self, xplan_item: XPlanungItem): + # setup details page, if not already present + if self.details_dialog.plan_xid != xplan_item.plan_xid: + await self.details_dialog.initPlanData(xplan_item.plan_xid) + + # find object + proxy_model = self.details_dialog.objectTree.proxy + index_list = proxy_model.match(proxy_model.index(0, 0), Qt.DisplayRole, xplan_item.xtype, -1, + Qt.MatchStartsWith | Qt.MatchRecursive | Qt.MatchWrap) + items = [proxy_model.itemAtIndex(i) for i in index_list] + i, item = next((i, item) for i, item in enumerate(items) if item._data.xid == xplan_item.xid) + + selection_model = self.details_dialog.objectTree.selectionModel() + selection_model.select(index_list[i], QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) + + self.details_dialog.objectTree.scrollTo(index_list[i], QAbstractItemView.PositionAtCenter) + + @qasync.asyncSlot(XPlanungItem) + async def onFeatureSaved(self, xplan_item: XPlanungItem): + # details window of plan is not currently open + if self.details_dialog.plan_xid != xplan_item.plan_xid: + return + + # parent is either specific parent item or bereich, or plan (if adding a bereich object) + parent_id = xplan_item.parent_xid or xplan_item.bereich_xid or xplan_item.plan_xid + + # find parent, to add object + model = self.details_dialog.objectTree.model + index_list = model.match(model.index(0, 0), XID_ROLE, parent_id, -1, Qt.MatchWildcard | Qt.MatchRecursive) + + if not index_list: + return + + await self.details_dialog.addExplorerItem(model.itemAtIndex(index_list[0]), xplan_item) + + @qasync.asyncSlot(QBuildingTemplateEdit) + async def showTemplateEdit(self, edit_widget: QBuildingTemplateEdit): + self.details_dialog.insertWidgetIntoNewPage(edit_widget) + self.openDetails() diff --git a/src/SAGisXPlanung/gui/__init__.py b/src/SAGisXPlanung/gui/__init__.py new file mode 100644 index 0000000..38647bc --- /dev/null +++ b/src/SAGisXPlanung/gui/__init__.py @@ -0,0 +1 @@ +from .po_annotations_dialog import CreateAnnotateDialog diff --git a/src/SAGisXPlanung/gui/actions/__init__.py b/src/SAGisXPlanung/gui/actions/__init__.py new file mode 100644 index 0000000..c1182fe --- /dev/null +++ b/src/SAGisXPlanung/gui/actions/__init__.py @@ -0,0 +1,3 @@ +from ._object_tree_actions import EnableBuldingTemplateAction, EditBuildingTemplateAction + +from ._context_menu_tool_actions import MoveAnnotationItemAction diff --git a/src/SAGisXPlanung/gui/actions/_context_menu_tool_actions.py b/src/SAGisXPlanung/gui/actions/_context_menu_tool_actions.py new file mode 100644 index 0000000..7fd1929 --- /dev/null +++ b/src/SAGisXPlanung/gui/actions/_context_menu_tool_actions.py @@ -0,0 +1,92 @@ +from qgis.PyQt.QtCore import pyqtSlot, pyqtSignal, QObject, QEvent, Qt +from qgis.PyQt.QtWidgets import QAction +from qgis.core import QgsPointXY, QgsAnnotationLayer, QgsGeometry +from qgis.utils import iface +from sqlalchemy.orm import load_only + +from SAGisXPlanung import Session +from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry +from SAGisXPlanung.XPlanungItem import XPlanungItem + + +class MoveAnnotationItemAction(QAction): + """ QAction zur Nutzung im ContextMenuTool. Kann auf Objekte des Typs XP_AbstraktesPräsentationsobjekt angewandt + werden, um die Position der zugehörigen Annotationen auf der Karte zu verändern. """ + + def __init__(self, item: XPlanungItem, parent=None): + + self.text = 'Präsentationsobjekt verschieben' + self.xplan_item = item + self.event_filter = None + self.annotation_layer = None + self.annotation_item = None + + super(MoveAnnotationItemAction, self).__init__(self.text, parent) + + self.triggered.connect(self.onActionTriggered) + + @pyqtSlot(bool) + def onActionTriggered(self, checked: bool): + self.event_filter = AnnotationMoveEventFilter(iface.mapCanvas(), self) + + self.annotation_layer: QgsAnnotationLayer = MapLayerRegistry().layerByFeature(self.xplan_item.xid) + + if not self.annotation_layer: + return + + for item_id, item in self.annotation_layer.items().items(): + id_prop = self.annotation_layer.customProperties().value(f'xplanung/feat-{item_id}') + if id_prop == str(self.xplan_item.xid): + self.annotation_item = item + break + + self.beginMove() + + def setCenter(self, point: QgsPointXY): + if not self.annotation_item: + return + self.annotation_item.setPoint(point) + self.annotation_layer.triggerRepaint() + + def beginMove(self): + iface.mapCanvas().viewport().installEventFilter(self.event_filter) + self.event_filter.initial_pos = self.annotation_item.point() + + def endMove(self): + iface.mapCanvas().viewport().removeEventFilter(self.event_filter) + + with Session.begin() as session: + xp_po = session.get(self.xplan_item.xtype, self.xplan_item.xid, [load_only('id', 'position')]) + + xp_po.setGeometry(QgsGeometry.fromPointXY(self.event_filter.last_pos)) + + +class AnnotationMoveEventFilter(QObject): + + def __init__(self, canvas, action, parent=None): + self.canvas = canvas + self.action = action + + self.last_pos = None + self.initial_pos = None + + super(AnnotationMoveEventFilter, self).__init__(parent) + + def eventFilter(self, obj, event): + # on mouse move let canvas item follow mouse position + if event.type() == QEvent.MouseMove: + point = self.canvas.getCoordinateTransform().toMapCoordinates(event.pos()) + self.action.setCenter(point) + self.last_pos = point + # on left click dont propagate the event and finish moving the canvas item + if event.type() == QEvent.MouseButtonRelease: + if event.button() == Qt.MiddleButton: + return False + if event.button() == Qt.LeftButton: + self.action.endMove() + if event.button() == Qt.RightButton: + self.action.setCenter(self.initial_pos) + self.initial_pos = None + self.action.endMove() + return True + return False diff --git a/src/SAGisXPlanung/gui/actions/_object_tree_actions.py b/src/SAGisXPlanung/gui/actions/_object_tree_actions.py new file mode 100644 index 0000000..56157ef --- /dev/null +++ b/src/SAGisXPlanung/gui/actions/_object_tree_actions.py @@ -0,0 +1,151 @@ +from qgis.PyQt.QtCore import pyqtSlot, pyqtSignal +from qgis.PyQt.QtWidgets import QAction +from sqlalchemy.orm import load_only, joinedload +from sqlalchemy.orm.attributes import flag_modified + +from SAGisXPlanung import Session +from SAGisXPlanung.BuildingTemplateItem import BuildingTemplateCellDataType, BuildingTemplateItem +from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry +from SAGisXPlanung.XPlan.XP_Praesentationsobjekte.feature_types import XP_Nutzungsschablone +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui.widgets import QBuildingTemplateEdit + + +class EnableBuldingTemplateAction(QAction): + """ QAction zur Nutzung im Objektbaum. Kann auf Objekte des Typs BP_BaugebietsTeilFlaeche angewandt werden, + um deren Nutzungschablone ein- oder auszublenden. Konfiguration wird in der Datenbank gespeichert.""" + + def __init__(self, item: XPlanungItem, parent=None): + + self.text = 'Nutzungsschablone anzeigen' + self.tooltip = 'Soll die Nutzungsschablone dieses Objekts auf der Karte angezeigt werden?' + self.parent_item = item + + super(EnableBuldingTemplateAction, self).__init__(self.text, parent) + + self.setToolTip(self.tooltip) + self.setCheckable(True) + + with Session.begin() as session: + bp_baugebiet = session.query(item.xtype).options( + load_only('id'), + # the following should in theory be better as it only emits one sql statement and loads only required columns + # but in practice it seems that the join query takes longer than two simple selects + # joinedload(XP_Objekt.wirdDargestelltDurch.of_type(XP_Nutzungsschablone)).load_only('id', 'hidden') + ).get(item.xid) + template: XP_Nutzungsschablone = bp_baugebiet.wirdDargestelltDurch[0] + self.template_id = template.id + self.setChecked(not template.hidden) + + self.toggled.connect(self.onActionToggled) + + @pyqtSlot(bool) + def onActionToggled(self, checked: bool): + with Session.begin() as session: + template: XP_Nutzungsschablone = session.query(XP_Nutzungsschablone).options( + load_only('id')).get(self.template_id) + template.hidden = not checked + + if not checked: + MapLayerRegistry().removeCanvasItems(self.parent_item.xid) + elif MapLayerRegistry().featureIsShown(self.parent_item.xid): + # display template on canvas by reloading layer + with Session.begin() as session: + bp_baugebiet = session.query(self.parent_item.xtype).get(self.parent_item.xid) + # this `toCanvas` call with no parameter only works because we already know that the layer is present + # on canvas through `featureIsShown` call before + bp_baugebiet.toCanvas(None) + + +class EditBuildingTemplateAction(QAction): + """ QAction zum Generieren eines Widgets, das bearbeiten einer Nuzungsschablone ermöglicht. """ + + editFormCreated = pyqtSignal(QBuildingTemplateEdit) + + def __init__(self, item: XPlanungItem, parent=None): + + self.text = 'Nutzungsschablone bearbeiten' + self.tooltip = 'Die Nutzungsschablone dieser Baugebietsteilfläche bearbeiten' + self.parent_item = item + + super(EditBuildingTemplateAction, self).__init__(self.text, parent) + + self.setToolTip(self.tooltip) + + with Session.begin() as session: + bp_baugebiet = session.query(item.xtype).options( + load_only('id'), + joinedload('wirdDargestelltDurch').load_only('id') + ).get(item.xid) + template: XP_Nutzungsschablone = bp_baugebiet.wirdDargestelltDurch[0] + self.template_id = template.id + + self.triggered.connect(self.onActionToggled) + + @pyqtSlot(bool) + def onActionToggled(self, checked: bool): + with Session.begin() as session: + template: XP_Nutzungsschablone = session.query(XP_Nutzungsschablone).get(self.template_id) + rows = template.zeilenAnz + scale = template.skalierung + angle = template.drehwinkel + if template.data_attributes is None: + template.data_attributes = BuildingTemplateCellDataType.as_default(int(rows)) + cells = template.data_attributes + + # generate widget, send via signal to dialog + edit_form = QBuildingTemplateEdit(cells, rows, scale=scale, angle=angle) + edit_form.cellDataChanged.connect(lambda cell, cell_i: self.onTemplateCellDataChanged(cell, cell_i)) + edit_form.rowCountChanged.connect(lambda row_count, cell_types: self.onRowCountChanged(row_count, cell_types)) + edit_form.styleChanged.connect(lambda k, v: self.onStyleChanged(k, v)) + self.editFormCreated.emit(edit_form) + + @pyqtSlot(str, object) + def onStyleChanged(self, attr: str, value): + with Session.begin() as session: + template: XP_Nutzungsschablone = session.query(XP_Nutzungsschablone).get(self.template_id) + setattr(template, attr, value) + + if not template.hidden and MapLayerRegistry().featureIsShown(self.parent_item.xid): + canvas_items = MapLayerRegistry().canvasItemsAtFeat(self.parent_item.xid) + template_canvas_item = next(x for x in canvas_items if isinstance(x, BuildingTemplateItem)) + + if attr == 'drehwinkel': + template_canvas_item.setAngle(value) + elif attr == 'skalierung': + template_canvas_item.setScale(value) + + @pyqtSlot(BuildingTemplateCellDataType, int) + def onTemplateCellDataChanged(self, cell: BuildingTemplateCellDataType, cell_index: int): + with Session.begin() as session: + template: XP_Nutzungsschablone = session.query(XP_Nutzungsschablone).get(self.template_id) + template.data_attributes[cell_index] = cell + # workaround for updating array element in database. arrays are not mutable in general + flag_modified(template, 'data_attributes') + + # update map layer registry immediately if template is currently visible + if not template.hidden and MapLayerRegistry().featureIsShown(self.parent_item.xid): + canvas_items = MapLayerRegistry().canvasItemsAtFeat(self.parent_item.xid) + template_canvas_item = next(x for x in canvas_items if isinstance(x, BuildingTemplateItem)) + + bp_baugebiet = session.query(self.parent_item.xtype).get(self.parent_item.xid) + template_canvas_item.setItemData(bp_baugebiet.usageTemplateData(template.data_attributes)) + template_canvas_item.updateCanvas() + + @pyqtSlot(int, list) + def onRowCountChanged(self, row_count: int, cells: list): + with Session.begin() as session: + template: XP_Nutzungsschablone = session.query(XP_Nutzungsschablone).get(self.template_id) + template.data_attributes = cells + template.zeilenAnz = row_count + # workaround for updating array element in database. arrays are not mutable in general + flag_modified(template, 'data_attributes') + + # update map layer registry immediately if template is currently visible + if not template.hidden and MapLayerRegistry().featureIsShown(self.parent_item.xid): + canvas_items = MapLayerRegistry().canvasItemsAtFeat(self.parent_item.xid) + template_canvas_item = next(x for x in canvas_items if isinstance(x, BuildingTemplateItem)) + + bp_baugebiet = session.query(self.parent_item.xtype).get(self.parent_item.xid) + template_canvas_item.setItemData(bp_baugebiet.usageTemplateData(template.data_attributes)) + template_canvas_item.setRowCount(row_count) diff --git a/src/SAGisXPlanung/gui/commands/__init__.py b/src/SAGisXPlanung/gui/commands/__init__.py new file mode 100644 index 0000000..e6bb64e --- /dev/null +++ b/src/SAGisXPlanung/gui/commands/__init__.py @@ -0,0 +1,2 @@ +from ._undo_commands import AttributeChangedCommand, ObjectsDeletedCommand +from ._stack import XPUndoStack diff --git a/src/SAGisXPlanung/gui/commands/_stack.py b/src/SAGisXPlanung/gui/commands/_stack.py new file mode 100644 index 0000000..8a6802d --- /dev/null +++ b/src/SAGisXPlanung/gui/commands/_stack.py @@ -0,0 +1,24 @@ +from typing import TypeVar, Type + +from qgis.PyQt.QtWidgets import QUndoStack, QUndoCommand + + +T = TypeVar('T', bound=QUndoCommand) + + +class XPUndoStack(QUndoStack): + """ Custom UndoStack with functionality to iterate over the stack contents""" + + def iterate(self, _type: Type[T] = None) -> T: + """ if _type parameter is specified, only filters on the given UndoCommand type""" + if _type is not None and not issubclass(_type, QUndoCommand): + raise TypeError('parameter `_type` must be a subclass of `QUndoCommand`') + + for i in range(self.count()): + command = self.command(i) + + if _type is not None: + if isinstance(command, _type): + yield command + else: + yield command diff --git a/src/SAGisXPlanung/gui/commands/_undo_commands.py b/src/SAGisXPlanung/gui/commands/_undo_commands.py new file mode 100644 index 0000000..4bb6075 --- /dev/null +++ b/src/SAGisXPlanung/gui/commands/_undo_commands.py @@ -0,0 +1,95 @@ +import inspect +from typing import List, Iterable + +from qgis.PyQt.QtCore import pyqtSignal, QModelIndex, QObject, Qt +from qgis.PyQt.QtWidgets import QUndoCommand +from sqlalchemy import update, delete, select, inspect as s_inspect +from sqlalchemy.orm import make_transient, selectinload + +from SAGisXPlanung import Session, Base +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.config import export_version +from SAGisXPlanung.gui.widgets.QExplorerView import ClassNode, XID_ROLE + + +class SignalProxy(QObject): + changeApplied = pyqtSignal(QModelIndex, str, object) # index, attr, value + + deleteReverted = pyqtSignal(ClassNode) + deleteApplied = pyqtSignal(ClassNode) + + +class AttributeChangedCommand(QUndoCommand): + def __init__(self, xplanung_item, attribute, previous_value, new_value, model_index): + super().__init__(f'Änderung Attribut {attribute} im Objekt {xplanung_item.xtype.__name__}') + self.xplan_item = xplanung_item + self.model_index = model_index + + self.attribute = attribute + self.previous_value = previous_value + self.new_value = new_value + + self.signal_proxy = SignalProxy() + + def setModelIndex(self, index: QModelIndex): + self.model_index = index + + def applyValue(self, value): + with Session.begin() as session: + session.expire_on_commit = False + + base_classes = [c for c in list(inspect.getmro(self.xplan_item.xtype)) if issubclass(c, Base)] + cls = next(c for c in reversed(base_classes) if hasattr(c, self.attribute) and c.attr_fits_version(self.attribute, export_version())) + + stmt = update(cls.__table__).where( + cls.__table__.c.id == self.xplan_item.xid + ).values({self.attribute: value}) + session.execute(stmt) + + def undo(self): + self.applyValue(self.previous_value) + self.signal_proxy.changeApplied.emit(self.model_index, self.attribute, self.previous_value) + + def redo(self): + self.applyValue(self.new_value) + self.signal_proxy.changeApplied.emit(self.model_index, self.attribute, self.new_value) + + +class ObjectsDeletedCommand(QUndoCommand): + def __init__(self, nodes: List[ClassNode], parent): + self.count = len(nodes) + super().__init__(f'Löschen {self.count} Objekt{"e" if self.count > 1 else ""}') + + self.parent = parent + + self.items = nodes + self.objects = [] + + self.signal_proxy = SignalProxy() + + def make_related_objects_transient(self, obj): + for rel_item in obj.related(): + make_transient(rel_item) + self.make_related_objects_transient(rel_item) + + def undo(self): + with Session.begin() as session: + for item, obj in zip(self.items, self.objects): + make_transient(obj) + self.make_related_objects_transient(obj) + session.add(obj) + + self.signal_proxy.deleteReverted.emit(item) + + def redo(self): + self.objects = [] + + with Session.begin() as session: + session.expire_on_commit = False + for item in self.items: + xp_item = item.xplanItem() + obj = session.get(xp_item.xtype, xp_item.xid, [selectinload('*')]) + session.delete(obj) + + self.objects.append(obj) + self.signal_proxy.deleteApplied.emit(item) diff --git a/src/SAGisXPlanung/gui/po_annotations_dialog.py b/src/SAGisXPlanung/gui/po_annotations_dialog.py new file mode 100644 index 0000000..0217a16 --- /dev/null +++ b/src/SAGisXPlanung/gui/po_annotations_dialog.py @@ -0,0 +1,214 @@ +import logging +import os +import uuid +from pathlib import Path + +import qasync +from qgis.PyQt import uic +from qgis.PyQt.QtWidgets import QDialogButtonBox +from qgis.PyQt.QtWidgets import QLabel, QVBoxLayout, QDialog +from qgis.PyQt.QtCore import pyqtSignal, Qt +from qgis.gui import QgsSvgSelectorWidget +from qgis.utils import iface +from qgis.core import QgsProject, QgsApplication + +from geoalchemy2 import WKBElement +from sqlalchemy.orm import load_only + +from SAGisXPlanung import BASE_DIR, Session +from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry +from SAGisXPlanung.XPlan.XP_Praesentationsobjekte.feature_types import XP_PPO, XP_PTO +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.config import SVG_CONFIG +from SAGisXPlanung.utils import save_to_db + +FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), '../ui/create_annotation.ui')) +logger = logging.getLogger(__name__) + + +class CreateAnnotateDialog(QDialog, FORM_CLASS): + """ Dialog zum Annotieren von Planwerken mit Präsentationsobjekten """ + + annotationSaved = pyqtSignal(XPlanungItem) + + def __init__(self, parent_item: XPlanungItem, parent=iface.mainWindow()): + super(CreateAnnotateDialog, self).__init__(parent) + self.setupUi(self) + + # set svg paths to xplanung symbol library + self.previous_svg_path = QgsApplication.svgPaths() + QgsApplication.setSvgPaths([os.path.join(BASE_DIR, 'symbole')]) + QgsApplication.setDefaultSvgPaths([os.path.join(BASE_DIR, 'symbole')]) + + self.parent_item = parent_item + self.selected_name = '' + self.selected_category = '' + self.label_name = QLabel(self.selected_name) + self.label_name.setObjectName('name') + self.category_name = QLabel(self.selected_category) + self.category_name.setObjectName('category') + + self._head_layout = QVBoxLayout() + self._head_layout.addWidget(self.category_name) + self._head_layout.addWidget(self.label_name) + self._head_layout.setSpacing(5) + self.head_layout_shown = False + + self.svg_widget = QgsSvgSelectorWidget() + self.svg_widget.sourceLineEdit().setVisible(False) + self.svg_widget.setSvgPath(os.path.join(BASE_DIR, 'symbole')) + self.svg_widget.svgSelected.connect(self.onSvgSelected) + self.symbol_tab.layout().addWidget(self.svg_widget) + self.symbol_tab.layout().setSpacing(20) + + self.save_button = self.buttonBox.button(QDialogButtonBox.Save) + self.save_button.setEnabled(False) + self.text_content.textEdited.connect(lambda text: self.save_button.setEnabled(bool(text))) + + self.tabs.tabBar().setCursor(Qt.PointingHandCursor) + + self.setStyleSheet(''' + QToolButton { + border: 0px; + } + #category { + text-transform: uppercase; + font-weight: 400; + color: #374151; + } + #name { + font-size: 1.125rem; + font-weight: 500; + color: #1c1917; + } + #error_message{ + font-weight: bold; + font-size: 7pt; + color: #991B1B; + } + QTabWidget::pane { + border: none; + border-top: 1px solid #e5e7eb; + position: absolute; + } + QTabWidget::tab-bar { + alignment: center; + } + /* Style the tab using the tab sub-control. Note that + it reads QTabBar _not_ QTabWidget */ + QTabBar::tab { + border: none; + min-width: 20ex; + padding: 15px; + color: #4b5563; + cursor: pointer; + } + + QTabBar::tab:hover { + background-color: #e5e7eb; + color: #111827; + } + + QTabBar::tab:selected { + border-bottom: 2px solid #93C5FD; + background-color: #eff6ff; + color: #111827; + } + ''') + + def closeEvent(self, e): + # restore projects svg paths on dialog close + QgsApplication.setDefaultSvgPaths(self.previous_svg_path) + QgsApplication.setSvgPaths(self.previous_svg_path) + + @qasync.asyncSlot(str) + async def onSvgSelected(self, path: str): + file_name = Path(path).name + symbol_node = SVG_CONFIG.get(file_name, "") + if not symbol_node: + self.error_message.setText('Dieses Symbol gehört nicht zum Symbolkatalog von SAGis XPlanung!') + self.save_button.setEnabled(False) + + self._head_layout.setParent(None) + self.category_name.setText('') + self.label_name.setText('') + self.head_layout_shown = False + return + + self.save_button.setEnabled(True) + self.error_message.clear() + + if not self.head_layout_shown: + self.symbol_tab.layout().insertLayout(0, self._head_layout) + self.head_layout_shown = True + + self.category_name.setText(symbol_node['category']) + self.label_name.setText(symbol_node['name']) + + def accept(self): + + # access parent object and calculate centroid + with Session.begin() as session: + parent = session.get(self.parent_item.xtype, self.parent_item.xid, + [load_only('id', 'position', 'gehoertZuBereich_id')]) + centroid = parent.geometry().centroid() + self.parent_item.bereich_xid = str(parent.gehoertZuBereich_id) + + # create new po object, save to database and draw on canvas + # case 1: symbol annotation + if self.tabs.currentIndex() == 0: + xp_ppo = XP_PPO() + xp_ppo.id = uuid.uuid4() + xp_ppo.dientZurDarstellungVon_id = self.parent_item.xid + xp_ppo.gehoertZuBereich_id = self.parent_item.bereich_xid + + file_name = Path(self.svg_widget.currentSvgPath()).name + symbol_node = SVG_CONFIG[file_name] + xp_ppo.symbol_path = os.path.join('symbole', symbol_node['category'], file_name) + + srid = QgsProject().instance().crs().postgisSrid() + xp_ppo.position = WKBElement(centroid.asWkb(), srid=srid) + + xplan_item = XPlanungItem( + xid=str(xp_ppo.id), + xtype=XP_PPO, + plan_xid=self.parent_item.plan_xid, + bereich_xid=self.parent_item.bereich_xid, + parent_xid=self.parent_item.xid, + ) + po_obj = xp_ppo + + # case 2: text annotation + else: + xp_pto = XP_PTO() + xp_pto.id = uuid.uuid4() + xp_pto.dientZurDarstellungVon_id = self.parent_item.xid + xp_pto.gehoertZuBereich_id = self.parent_item.bereich_xid + + srid = QgsProject().instance().crs().postgisSrid() + xp_pto.position = WKBElement(centroid.asWkb(), srid=srid) + + xp_pto.schriftinhalt = self.text_content.text() + + xplan_item = XPlanungItem( + xid=str(xp_pto.id), + xtype=XP_PTO, + plan_xid=self.parent_item.plan_xid, + bereich_xid=self.parent_item.bereich_xid, + parent_xid=self.parent_item.xid, + ) + + po_obj = xp_pto + + save_to_db(po_obj, expire_on_commit=False) + self.annotationSaved.emit(xplan_item) + + # find parent layer to access plan group + layer = MapLayerRegistry().layerByXid(self.parent_item) + root = QgsProject.instance().layerTreeRoot() + tree_layer = root.findLayer(layer.id()) + + # display item on canvas immediately + po_obj.toCanvas(tree_layer.parent(), xplan_item.plan_xid) + + super(CreateAnnotateDialog, self).accept() diff --git a/src/SAGisXPlanung/gui/resources/delete.svg b/src/SAGisXPlanung/gui/resources/delete.svg new file mode 100644 index 0000000..18b037a --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/edit.svg b/src/SAGisXPlanung/gui/resources/edit.svg new file mode 100644 index 0000000..c4f727b --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/expand_less.svg b/src/SAGisXPlanung/gui/resources/expand_less.svg new file mode 100644 index 0000000..7c8d75a --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/expand_less.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/grid2x2.svg b/src/SAGisXPlanung/gui/resources/grid2x2.svg new file mode 100644 index 0000000..8da1899 --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/grid2x2.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/grid2x3.svg b/src/SAGisXPlanung/gui/resources/grid2x3.svg new file mode 100644 index 0000000..61b3ee0 --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/grid2x3.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/grid2x4.svg b/src/SAGisXPlanung/gui/resources/grid2x4.svg new file mode 100644 index 0000000..b09b8b6 --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/grid2x4.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/info.svg b/src/SAGisXPlanung/gui/resources/info.svg new file mode 100644 index 0000000..3c231a9 --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/location_edit.svg b/src/SAGisXPlanung/gui/resources/location_edit.svg new file mode 100644 index 0000000..0205627 --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/location_edit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/map.svg b/src/SAGisXPlanung/gui/resources/map.svg new file mode 100644 index 0000000..e2a57bd --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/map.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/minus.svg b/src/SAGisXPlanung/gui/resources/minus.svg new file mode 100644 index 0000000..7f92378 --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/minus.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/plus.svg b/src/SAGisXPlanung/gui/resources/plus.svg new file mode 100644 index 0000000..6130616 --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/plus.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/redo.svg b/src/SAGisXPlanung/gui/resources/redo.svg new file mode 100644 index 0000000..9846281 --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/redo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/sagis_icon.png b/src/SAGisXPlanung/gui/resources/sagis_icon.png new file mode 100644 index 0000000..22cacbd Binary files /dev/null and b/src/SAGisXPlanung/gui/resources/sagis_icon.png differ diff --git a/src/SAGisXPlanung/gui/resources/sort_alpha.svg b/src/SAGisXPlanung/gui/resources/sort_alpha.svg new file mode 100644 index 0000000..1c0dbf9 --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/sort_alpha.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/sort_category.svg b/src/SAGisXPlanung/gui/resources/sort_category.svg new file mode 100644 index 0000000..d332d95 --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/sort_category.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/sort_hierarchy.svg b/src/SAGisXPlanung/gui/resources/sort_hierarchy.svg new file mode 100644 index 0000000..935f2cd --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/sort_hierarchy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/table.svg b/src/SAGisXPlanung/gui/resources/table.svg new file mode 100644 index 0000000..42cb0e1 --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/table.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/undo.svg b/src/SAGisXPlanung/gui/resources/undo.svg new file mode 100644 index 0000000..5fd832e --- /dev/null +++ b/src/SAGisXPlanung/gui/resources/undo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/resources/xplanung_icon.png b/src/SAGisXPlanung/gui/resources/xplanung_icon.png new file mode 100644 index 0000000..a3c7dc7 Binary files /dev/null and b/src/SAGisXPlanung/gui/resources/xplanung_icon.png differ diff --git a/src/SAGisXPlanung/gui/style/__init__.py b/src/SAGisXPlanung/gui/style/__init__.py new file mode 100644 index 0000000..7c1415e --- /dev/null +++ b/src/SAGisXPlanung/gui/style/__init__.py @@ -0,0 +1,2 @@ +from .styles import TagStyledDelegate, HighlightRowProxyStyle, HighlightRowDelegate, FlagNewRole +from .svg import SVGButtonEventFilter, load_svg diff --git a/src/SAGisXPlanung/gui/style/styles.py b/src/SAGisXPlanung/gui/style/styles.py new file mode 100644 index 0000000..7dc7c41 --- /dev/null +++ b/src/SAGisXPlanung/gui/style/styles.py @@ -0,0 +1,75 @@ +import logging + +from qgis.PyQt.QtCore import QModelIndex, QRect, Qt +from qgis.PyQt.QtGui import QPainter, QFontMetrics, QPen, QColor, QFont, QPalette, QBrush +from qgis.PyQt.QtWidgets import QStyledItemDelegate, QStyleOptionViewItem, QProxyStyle, QStyleOption, QStyle + +logger = logging.getLogger(__name__) + +FlagNewRole = Qt.UserRole + 1 + + +class HighlightRowDelegate(QStyledItemDelegate): + + def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex): + option.palette.setBrush(QPalette.Highlight, QBrush(QColor('#CBD5E1'))) + option.palette.setBrush(QPalette.HighlightedText, QBrush(Qt.black)) + + super(HighlightRowDelegate, self).paint(painter, option, index) + + +class TagStyledDelegate(HighlightRowDelegate): + + margin_x = 10 + padding = 2 + + def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex): + + super(TagStyledDelegate, self).paint(painter, option, index) + + if not index.data(role=FlagNewRole): + return + + self.initStyleOption(option, index) + painter.save() + painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.HighQualityAntialiasing) + + fm_data = QFontMetrics(option.font) + text_width = fm_data.horizontalAdvance(index.data()) + tag_rect = QRect(option.rect) + tag_rect.setLeft(tag_rect.x() + text_width + self.margin_x) + painter.setPen(QPen(QColor('#6B7280'))) + tag_font = QFont(option.font) + tag_font.setPointSize(tag_font.pointSize() - 2) + painter.setFont(tag_font) + painter.drawText(tag_rect, option.displayAlignment | Qt.AlignVCenter, 'NEU') + fm_tag = QFontMetrics(tag_font) + border_rect = QRect(tag_rect) + border_rect.setLeft(border_rect.left() - self.padding) + border_rect.setTop(border_rect.top() + 0.5*self.padding) + border_rect.setBottom(border_rect.bottom() - 0.5*self.padding) + border_rect.setRight(border_rect.left() + fm_tag.horizontalAdvance('NEU') + 2*self.padding) + painter.setPen(QPen(QColor('#16A34A'))) + painter.drawRoundedRect(border_rect, 5, 5) + + painter.restore() + + +class HighlightRowProxyStyle(QProxyStyle): + + def drawPrimitive(self, element, option: QStyleOption, painter: QPainter, widget=None): + if element == QStyle.PE_PanelItemViewRow or element == QStyle.PE_PanelItemViewItem: + opt = QStyleOptionViewItem(option) + painter.save() + + if opt.state & QStyle.State_Selected: + painter.fillRect(opt.rect, QColor('#CBD5E1')) + elif opt.state & QStyle.State_MouseOver: + painter.fillRect(opt.rect, QColor('#E2E8F0')) + + painter.restore() + return + elif element == QStyle.PE_FrameFocusRect: + return + super(HighlightRowProxyStyle, self).drawPrimitive(element, option, painter) diff --git a/src/SAGisXPlanung/gui/style/svg.py b/src/SAGisXPlanung/gui/style/svg.py new file mode 100644 index 0000000..b8aa78b --- /dev/null +++ b/src/SAGisXPlanung/gui/style/svg.py @@ -0,0 +1,30 @@ +from qgis.PyQt.QtGui import QPixmap, QPainter, QColor, QIcon +from qgis.PyQt.QtCore import QObject, QEvent, QSize + + +def load_svg(svg, color=None): + img = QPixmap(svg) + if color: + qp = QPainter(img) + qp.setCompositionMode(QPainter.CompositionMode_SourceIn) + qp.fillRect(img.rect(), QColor(color)) + qp.end() + return QIcon(img) + + +class SVGButtonEventFilter(QObject): + """ Eventfilter to style common toolbutton widgets that use a svg icon""" + + def __init__(self, color='#6B7280', hover_color='#1F2937'): + super().__init__() + self.color = color + self.hover_color = hover_color + + def eventFilter(self, obj, event): + if event.type() == QEvent.HoverEnter: + obj.setIcon(load_svg(obj.icon().pixmap(obj.icon().actualSize(QSize(32, 32))), color=self.hover_color)) + elif event.type() == QEvent.HoverLeave: + obj.setIcon(load_svg(obj.icon().pixmap(obj.icon().actualSize(QSize(32, 32))), color=self.color)) + return False + + diff --git a/src/SAGisXPlanung/gui/widgets/QAttributeEdit.py b/src/SAGisXPlanung/gui/widgets/QAttributeEdit.py new file mode 100644 index 0000000..04fa9fb --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QAttributeEdit.py @@ -0,0 +1,256 @@ +import datetime +import inspect +import logging +import os +from collections import namedtuple + +import qasync +from geoalchemy2 import WKBElement, WKTElement + +from qgis.PyQt import uic +from qgis.PyQt.QtCore import QAbstractTableModel, Qt, QSortFilterProxyModel, pyqtSlot, QModelIndex, QRegExp, pyqtSignal +from qgis.PyQt.QtWidgets import QHeaderView, QLineEdit +from qgis.PyQt.QtGui import QIcon, QRegExpValidator +from sqlalchemy import update + +from SAGisXPlanung import BASE_DIR, Session, Base +from SAGisXPlanung.GML.geometry import geometry_from_spatial_element +from SAGisXPlanung.RuleBasedSymbolRenderer import RuleBasedSymbolRenderer +from SAGisXPlanung.XPlan.XP_Praesentationsobjekte.feature_types import XP_AbstraktesPraesentationsobjekt +from SAGisXPlanung.XPlan.feature_types import XP_Plan, XP_Objekt +from SAGisXPlanung.XPlan.mixins import XPlanungEnumMixin, ElementOrderMixin +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.config import xplan_tooltip, export_version +from SAGisXPlanung.gui.XPEditAttributeDialog import XPEditAttributeDialog +from SAGisXPlanung.gui.commands import AttributeChangedCommand +from SAGisXPlanung.gui.widgets.QRelationDropdowns import QAddRelationDropdown + +FORM_CLASS, CLS = uic.loadUiType(os.path.join(BASE_DIR, 'ui/attribute_edit.ui')) +logger = logging.getLogger(__name__) + +ObjectRole = Qt.UserRole + 1 + + +class QAttributeEdit(CLS, FORM_CLASS): + nameChanged = pyqtSignal(str) + + ICON_DEFAULT_SIZE = 24 + TEXT_DEFAULT_SIZE = 6 + ATTRIBUTE_SIZE = 'skalierung' + ATTRIBUTE_ANGLE = 'drehwinkel' + + @staticmethod + def create(xplanung_item: XPlanungItem, data, parent): + if issubclass(xplanung_item.xtype, XP_AbstraktesPraesentationsobjekt): + from SAGisXPlanung.gui.widgets.QAttributeEditAnnotationItem import QAttributeEditAnnotationItem + return QAttributeEditAnnotationItem(xplanung_item, data, parent) + elif hasattr(xplanung_item.xtype, 'renderer') and isinstance(xplanung_item.xtype.renderer(xplanung_item.geom_type), RuleBasedSymbolRenderer): + from SAGisXPlanung.gui.widgets.QAttributeEditSymbolRenderer import QAttributeEditSymbolRenderer + return QAttributeEditSymbolRenderer(xplanung_item, data, parent) + else: + return QAttributeEdit(xplanung_item, data, parent) + + def __init__(self, xplanung_item: XPlanungItem, data, parent): + super(QAttributeEdit, self).__init__(parent) + self.setupUi(self) + self.parent = parent + self._xplanung_item = xplanung_item + + self.editSearch.addAction(QIcon(':/images/themes/default/search.svg'), QLineEdit.LeadingPosition) + reg_ex = QRegExp(r'\d{1,3}°?') + self.angleEdit.setValidator(QRegExpValidator(reg_ex, self.angleEdit)) + + # model setup + edit_allowed = not issubclass(self._xplanung_item.xtype, XP_Objekt) + self.model = AttributeTableModel(xplanung_item, data, edit=edit_allowed) + self.proxyModel = QSortFilterProxyModel(self.tableView) + self.proxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive) + self.proxyModel.setSourceModel(self.model) + self.tableView.setModel(self.proxyModel) + + # table header setup + header = self.tableView.horizontalHeader() + header.setSectionResizeMode(QHeaderView.Stretch) + header.setSectionResizeMode(0, QHeaderView.ResizeToContents) + self.tableView.verticalHeader().hide() + + self.styleGroup.setVisible(False) + + self.tableView.doubleClicked.connect(self.onDoubleClicked) + self.editSearch.textChanged.connect(self.onFilterTextChanged) + + def set_form_values(self): + with Session.begin() as session: + plan_content = session.query(self._xplanung_item.xtype).get(self._xplanung_item.xid) + scale = getattr(plan_content, self.ATTRIBUTE_SIZE) + self.sizeSlider.setValue(scale * (99 - 1) + 1) + angle = getattr(plan_content, self.ATTRIBUTE_ANGLE) + self.angleEdit.setText(f'{angle}°') + self.angleDial.setValue(angle) + + def initialize_listeners(self): + self.sizeSlider.valueChanged.connect(self.onSliderValueChanged) + self.sizeSlider.sliderReleased.connect(lambda a=self.ATTRIBUTE_SIZE: self.onSliderReleased(a)) + self.angleDial.valueChanged.connect(self.onDialValueChanged) + self.angleDial.sliderReleased.connect(lambda a=self.ATTRIBUTE_ANGLE: self.onSliderReleased(a)) + self.angleEdit.editingFinished.connect(self.onAngleTextEdited) + + def model_index(self, attr: str): + indices = self.model.match(self.model.index(0, 0), Qt.DisplayRole, attr, 1, Qt.MatchFixedString) + if not indices: + return QModelIndex() + return indices[0].siblingAtColumn(1) + + @pyqtSlot() + def onAngleTextEdited(self): + text = self.angleEdit.text().replace('°', '') + self.angleDial.setValue(int(text)) + self.onSliderReleased(self.ATTRIBUTE_ANGLE) + + @pyqtSlot(str) + def onFilterTextChanged(self, text: str): + self.proxyModel.setFilterFixedString(text) + + @pyqtSlot(QModelIndex) + def onDoubleClicked(self, index: QModelIndex): + if index.column() == 0: + return + index = self.proxyModel.mapToSource(index) + attribute_name = index.siblingAtColumn(0).data() + + rel = next((r for r in self._xplanung_item.xtype.relationships() if r[0] == attribute_name), None) + if rel is not None: + dlg = XPEditAttributeDialog(attribute_name, None, index.data(), self._xplanung_item.xtype, parent=self) + stub = namedtuple('stub', ['cls_type']) + cb = QAddRelationDropdown(stub(cls_type=self._xplanung_item.xtype), rel) + cb.setDefault(index.data(role=ObjectRole)) + + dlg.control.hide() + dlg.hl1.addWidget(cb) + dlg.control = cb + else: + base_classes = [c for c in list(inspect.getmro(self._xplanung_item.xtype)) if issubclass(c, Base)] + cls = next(c for c in base_classes if hasattr(c, attribute_name) and c.attr_fits_version(attribute_name, export_version())) + field_type = getattr(cls, attribute_name).property.columns[0].type + dlg = XPEditAttributeDialog(attribute_name, field_type, index.data(), self._xplanung_item.xtype, parent=self) + + dlg.attributeChanged.connect(lambda original, value, a=attribute_name, i=index: + self.onAttributeChanged(i, a, value)) + dlg.attributeChanged.connect(lambda original, value, a=attribute_name, i=index: + self.pushAttributeChangedCommand(original, value, a, i)) + dlg.fileChanged.connect(lambda value: self.onAttributeChanged(None, 'file', value)) + dlg.exec_() + + def pushAttributeChangedCommand(self, original_value, new_value, attr, index): + command = AttributeChangedCommand( + xplanung_item=self._xplanung_item, + attribute=attr, + previous_value=original_value, + new_value=new_value, + model_index=index + ) + command.signal_proxy.changeApplied.connect(self.onChangeApplied) + self.parent.undo_stack.push(command) + + @pyqtSlot(QModelIndex, str, object) + def onChangeApplied(self, index, attr, value): + if index is not None: + self.model.setData(index, value) + + # send name changes to update other ui components + if issubclass(self._xplanung_item.xtype, XP_Plan) and attr == 'name': + self.nameChanged.emit(value) + + def onAttributeChanged(self, index, attr, value): + with Session.begin() as session: + session.expire_on_commit = False + + base_classes = [c for c in list(inspect.getmro(self._xplanung_item.xtype)) if issubclass(c, Base)] + cls = next(c for c in reversed(base_classes) if hasattr(c, attr) and c.attr_fits_version(attr, export_version())) + + stmt = update(cls.__table__).where( + cls.__table__.c.id == self._xplanung_item.xid + ).values({attr: value}) + session.execute(stmt) + + self.onChangeApplied(index, attr, value) + + def onSliderReleased(self, attr): + if attr == self.ATTRIBUTE_SIZE: + slider_value = self.sizeSlider.value() + scale = (slider_value - 1) / (99 - 1) + value = f'{scale:.2f}' + elif attr == self.ATTRIBUTE_ANGLE: + value = self.angleDial.value() + else: + raise Exception('invalid attribute') + + with Session.begin() as session: + plan_content = session.query(self._xplanung_item.xtype).get(self._xplanung_item.xid) + setattr(plan_content, attr, value) + + +class AttributeTableModel(QAbstractTableModel): + def __init__(self, xplanung_item: XPlanungItem, data, edit=True): + super(AttributeTableModel, self).__init__() + self.edit_allowed = edit + self._xplanung_item = xplanung_item + self._data = data + self._horizontal_header = ['XPlanung-Attribut', 'Wert'] + + @staticmethod + def parser(value): + if isinstance(value, datetime.date): + return value.strftime("%d.%m.%Y") + return str(value) + + def data(self, index, role): + if role == Qt.DisplayRole: + value = self._data[index.row()][index.column()] + if isinstance(value, (WKBElement, WKTElement)): + return geometry_from_spatial_element(value).asWkt() + if isinstance(value, (XPlanungEnumMixin, ElementOrderMixin)): + return str(value) + if isinstance(value, datetime.date): + return value.strftime("%d.%m.%Y") + if isinstance(value, list): + return ", ".join(map(AttributeTableModel.parser, value)) + return value + if role == ObjectRole: + value = self._data[index.row()][index.column()] + return value + if role == Qt.ToolTipRole: + if not self.edit_allowed: + return "Editieren von Attributen nur in der Vollversion möglich" + # show tooltips for first column, which are the xplanung attributes + if index.column() != 0: + return + return xplan_tooltip(self._xplanung_item.xtype, self._data[index.row()][index.column()]) + + def setData(self, index: QModelIndex, value, role=Qt.DisplayRole): + if role == Qt.DisplayRole: + self._data[index.row()][index.column()] = value + self.dataChanged.emit(index, index) + + def headerData(self, col, orientation, role): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self._horizontal_header[col] + + def flags(self, index): + current_flags = super(AttributeTableModel, self).flags(index) + if not self.edit_allowed: + return current_flags & ~Qt.ItemIsEnabled + + attribute_name = self._data[index.row()][0] + xtype = self._xplanung_item.xtype + if hasattr(xtype, '__readonly_columns__') and attribute_name in xtype.__readonly_columns__: + return current_flags & ~Qt.ItemIsEnabled + if index.column() == 0: + return current_flags & ~Qt.ItemIsSelectable + return current_flags + + def rowCount(self, index): + return len(self._data) + + def columnCount(self, index): + return 2 diff --git a/src/SAGisXPlanung/gui/widgets/QAttributeEditAnnotationItem.py b/src/SAGisXPlanung/gui/widgets/QAttributeEditAnnotationItem.py new file mode 100644 index 0000000..d89d7a7 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QAttributeEditAnnotationItem.py @@ -0,0 +1,89 @@ +import logging +import os + +import qasync +from qgis.PyQt.QtCore import Qt, pyqtSlot +from qgis.core import QgsTextFormat +from sqlalchemy import select +from sqlalchemy.orm import load_only + +from SAGisXPlanung import Session, BASE_DIR +from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui.widgets import SVGSymbolDisplayWidget +from SAGisXPlanung.gui.widgets.QAttributeEdit import QAttributeEdit + +logger = logging.getLogger(__name__) + + +class QAttributeEditAnnotationItem(QAttributeEdit): + + def __init__(self, xplanung_item: XPlanungItem, data, parent=None): + super(QAttributeEditAnnotationItem, self).__init__(xplanung_item, data, parent) + + self.annotation_type = xplanung_item.xtype.__name__ + self._annotation_item = None + + self.styleGroup.setVisible(True) + + self._annotation_layer = MapLayerRegistry().layerByXid(self._xplanung_item) + if self._annotation_layer: + self.annotation_type = self._annotation_layer.customProperties().value(f'xplanung/type') + for item_id, item in self._annotation_layer.items().items(): + id_prop = self._annotation_layer.customProperties().value(f'xplanung/feat-{item_id}') + if id_prop == self._xplanung_item.xid: + self._annotation_item = item + + break + + # set initial form values + self.set_form_values() + self.initialize_listeners() + + # setup svg symbol display widget + if self.annotation_type == 'XP_PPO': + # don't query database if not required + if self._annotation_layer and self._annotation_item: + svg_path = self._annotation_item.format().background().svgFile() + else: + with Session.begin() as session: + annotation_item = session.get(xplanung_item.xtype, xplanung_item.xid, + [load_only('id', 'symbol_path')]) + svg_path = os.path.join(BASE_DIR, annotation_item.symbol_path or '') + + symbol_widget = SVGSymbolDisplayWidget(svg_path) + symbol_widget.svg_selection_saved.connect(self.onSvgSelected) + self.layout().addWidget(symbol_widget) + + @qasync.asyncSlot(str) + async def onSvgSelected(self, path: str): + self.onAttributeChanged(None, 'symbol_path', path) + + @pyqtSlot(int) + def onSliderValueChanged(self, value: int): + scale = (value - 1) / (99 - 1) + if self._annotation_item: + text_format: QgsTextFormat = self._annotation_item.format() + if self.annotation_type == 'XP_PTO': + text_format.setSize(self.TEXT_DEFAULT_SIZE * scale * 2.0) + else: + text_format.setSize(self.ICON_DEFAULT_SIZE * scale * 2.0) + self._annotation_item.setFormat(text_format) + self._annotation_layer.triggerRepaint() + + indices = self.model.match(self.model.index(0, 0), Qt.DisplayRole, self.ATTRIBUTE_SIZE, 1, Qt.MatchFixedString) + if not indices: + return + self.model.setData(indices[0].siblingAtColumn(1), f'{scale:.2f}') + + @pyqtSlot(int) + def onDialValueChanged(self, value: int): + self.angleEdit.setText(f'{value}°') + if self._annotation_item: + self._annotation_item.setAngle(value) + self._annotation_layer.triggerRepaint() + + indices = self.model.match(self.model.index(0, 0), Qt.DisplayRole, self.ATTRIBUTE_ANGLE, 1, Qt.MatchFixedString) + if not indices: + return + self.model.setData(indices[0].siblingAtColumn(1), value) diff --git a/src/SAGisXPlanung/gui/widgets/QAttributeEditSymbolRenderer.py b/src/SAGisXPlanung/gui/widgets/QAttributeEditSymbolRenderer.py new file mode 100644 index 0000000..4d9e098 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QAttributeEditSymbolRenderer.py @@ -0,0 +1,63 @@ +import logging + +from qgis.PyQt.QtCore import pyqtSlot +from qgis.core import edit, QgsFeatureRequest, QgsProject + +from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui.widgets.QAttributeEdit import QAttributeEdit + +logger = logging.getLogger(__name__) + + +class QAttributeEditSymbolRenderer(QAttributeEdit): + + def __init__(self, xplanung_item: XPlanungItem, data, parent=None): + super(QAttributeEditSymbolRenderer, self).__init__(xplanung_item, data, parent) + + self.styleGroup.setVisible(True) + + self._layer = MapLayerRegistry().layerByXid(self._xplanung_item, geom_type=xplanung_item.geom_type) + self._feature = None + if self._layer: + QgsProject.instance().layerStore().layerWillBeRemoved.connect(self.onLayerRemoved) + self.get_feature() + + # set initial form values + self.set_form_values() + self.initialize_listeners() + + def get_feature(self): + for feat in self._layer.getFeatures( + QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setNoAttributes()): + id_prop = self._layer.customProperties().value(f'xplanung/feat-{feat.id()}') + if id_prop == self._xplanung_item.xid: + self._feature = feat + break + + @pyqtSlot(str) + def onLayerRemoved(self, layer_id): + if self._layer is None: + return + if layer_id == self._layer.id(): + self._layer = None + + @pyqtSlot(int) + def onSliderValueChanged(self, value: int): + scale = (value - 1) / (99 - 1) + if not self._layer: + return + + field_idx = self._layer.fields().indexOf(self.ATTRIBUTE_SIZE) + with edit(self._layer): + self._layer.changeAttributeValue(self._feature.id(), field_idx, str(scale)) + + @pyqtSlot(int) + def onDialValueChanged(self, value: int): + self.angleEdit.setText(f'{value}°') + if not self._layer: + return + + field_idx = self._layer.fields().indexOf(self.ATTRIBUTE_ANGLE) + with edit(self._layer): + self._layer.changeAttributeValue(self._feature.id(), field_idx, str(value)) diff --git a/src/SAGisXPlanung/gui/widgets/QCollapsibleSearch.py b/src/SAGisXPlanung/gui/widgets/QCollapsibleSearch.py new file mode 100644 index 0000000..cb69d31 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QCollapsibleSearch.py @@ -0,0 +1,87 @@ + +from qgis.PyQt.QtGui import QIcon, QPixmap, QPainter, QColor, QFocusEvent, QKeyEvent +from qgis.PyQt.QtWidgets import QLineEdit, QToolButton, QProxyStyle, QStyle, QApplication +from qgis.PyQt.QtCore import QEvent, QSize, Qt, pyqtSlot + + +class QCollapsibleSearch(QLineEdit): + + def __init__(self, parent=None): + super(QCollapsibleSearch, self).__init__(parent) + + self.search_icon_action = self.addAction(QIcon(':/images/themes/default/search.svg'), QLineEdit.LeadingPosition) + self.search_icon_action.triggered.connect(self.onSearchActionTriggered) + self.search_widget = [w for w in self.search_icon_action.associatedWidgets() if isinstance(w, QToolButton)][0] + self.search_widget.setObjectName('search-icon') + self.search_widget.installEventFilter(self) + self.search_widget.setCursor(Qt.PointingHandCursor) + + self.proxy_style = ClearIconProxyStyle('Fusion') + self.proxy_style.setParent(self) + self.setStyle(self.proxy_style) + + self.setCursor(Qt.ArrowCursor) + self.setMaximumWidth(30) + self.setProperty('expanded', False) + + self.setStyleSheet(''' + * [expanded=false] { + border: none; + background-color: palette(window); + } + ''') + + @pyqtSlot(bool) + def onSearchActionTriggered(self, checked: bool): + if self.property('expanded'): + return + self.setMaximumWidth(200) + self.setProperty('expanded', True) + self.style().unpolish(self) + self.style().polish(self) + self.setCursor(Qt.IBeamCursor) + self.setFocus() + self.search_widget.removeEventFilter(self) + + def focusInEvent(self, evt: QFocusEvent): + super(QCollapsibleSearch, self).focusInEvent(evt) + + if not self.property('expanded'): + self.clearFocus() + + def focusOutEvent(self, evt: QFocusEvent): + super(QCollapsibleSearch, self).focusOutEvent(evt) + + if not self.property('expanded') or self.text(): + return + self.setMaximumWidth(30) + self.setProperty('expanded', False) + self.style().unpolish(self) + self.style().polish(self) + self.setCursor(Qt.ArrowCursor) + self.search_widget.installEventFilter(self) + self.search_widget.setIcon(self.loadSvg( + self.search_widget.icon().pixmap(self.search_widget.icon().actualSize(QSize(32, 32))), color='#6B7280')) + + def eventFilter(self, obj, event): + if event.type() == QEvent.HoverEnter: + obj.setIcon(self.loadSvg(obj.icon().pixmap(obj.icon().actualSize(QSize(32, 32))), color='#1F2937')) + elif event.type() == QEvent.HoverLeave: + obj.setIcon(self.loadSvg(obj.icon().pixmap(obj.icon().actualSize(QSize(32, 32))), color='#6B7280')) + return False + + def loadSvg(self, svg, color=None): + img = QPixmap(svg) + if color: + qp = QPainter(img) + qp.setCompositionMode(QPainter.CompositionMode_SourceIn) + qp.fillRect(img.rect(), QColor(color)) + qp.end() + return QIcon(img) + + +class ClearIconProxyStyle(QProxyStyle): + def standardIcon(self, standard_icon, option=None, widget=None): + if standard_icon == QStyle.SP_LineEditClearButton and isinstance(widget, QCollapsibleSearch): + return QIcon(':/images/themes/default/mIconClearText.svg') + return super().standardIcon(standard_icon, option, widget) diff --git a/src/SAGisXPlanung/gui/widgets/QCustomTreeWidgetItems.py b/src/SAGisXPlanung/gui/widgets/QCustomTreeWidgetItems.py new file mode 100644 index 0000000..11a1dc3 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QCustomTreeWidgetItems.py @@ -0,0 +1,149 @@ +from enum import Enum + +from geoalchemy2 import Geometry +from geoalchemy2.shape import to_shape + +from qgis.PyQt import QtWidgets, QtGui +from qgis.gui import QgsGeometryRubberBand, QgsVertexMarker +from qgis.core import QgsPolygon, QgsWkbTypes, QgsPointXY, QgsRectangle, QgsGeometry +from qgis.utils import iface + + +class GeometryIntersectionType(Enum): + """ Gibt an, welcher Grund einen Überschneidungsfehler hevorgerufen hat. """ + + Planinhalt = 'Flächenschlussobjekt weist Überschneidung auf' + Bereich = 'Planinhalt liegt nicht vollständig im Bereich' + Plan = 'Bereich liegt nicht vollständig im Geltungsbereich des Plans' + NotCovered = 'Kein Flächenschluss vorliegend' + + +class QFilePathTreeWidgetItem(QtWidgets.QTreeWidgetItem): + """ QTreeWidgetItem, dass zusätzlich einen Dateipfad speichert. + Nützlich für die Auswahl von Dateien in einem QTreeWidget """ + def __init__(self, *args, **kwargs): + self.filepath = kwargs.pop('filepath') + super(QFilePathTreeWidgetItem, self).__init__(*args, **kwargs) + + +class QIdentifierTreeWidgetItem(QtWidgets.QTreeWidgetItem): + """ QTreeWidgetItem, dass zusätzlich die UUID und die Klasse eines Objekts des XPlanung Objektmodells speichert. """ + def __init__(self, *args, uid=None, cls_type=None, parent_attribute=None, **kwargs): + self.xplanung_id = uid + self.xplanung_type = cls_type + self.parent_attribute = parent_attribute + super().__init__(*args, **kwargs) + + +class QGeometryValidationTreeWidgetItem(QIdentifierTreeWidgetItem): + """ Abstrakte Oberklasse für QTreeWidgetItems, die Fehler bei der Geometrievalidierung enthalten""" + def __init__(self, *args, uid=None, cls_type=None, error_msg=None, **kwargs): + super().__init__(*args, uid=uid, cls_type=cls_type, **kwargs) + self.error_msg = error_msg + self.setText(0, self.xplanung_type.__name__) + self.setText(1, self.error_msg) + self.isVisible = False + + def removeFromCanvas(self): + raise NotImplementedError + + def displayErrorOnCanvas(self): + raise NotImplementedError + + def extent(self): + raise NotImplementedError + + +class QGeometryPolygonTreeWidgetItem(QGeometryValidationTreeWidgetItem): + """ QTreeWidgetItem, dass Daten zu einem Überschneidungsfehler bei der Geometrievalidierung hält""" + + def __init__(self, *args, uid=None, cls_type=None, polygon=None, + intersection_type=GeometryIntersectionType.Planinhalt, **kwargs): + self.error_msg = intersection_type.value + super().__init__(*args, uid=uid, cls_type=cls_type, error_msg=self.error_msg, **kwargs) + + self.polygon = QgsPolygon() + self.polygon.fromWkt(polygon) + self.rubber_band = QgsGeometryRubberBand(iface.mapCanvas(), QgsWkbTypes.PolygonGeometry) + self.rubber_band.setFillColor(QtGui.QColor(0, 0, 0, 0)) + self.rubber_band.setStrokeWidth(3) + self.rubber_band.setGeometry(self.polygon) + self.removeFromCanvas() + + def __del__(self): + iface.mapCanvas().scene().removeItem(self.rubber_band) + + def removeFromCanvas(self): + self.rubber_band.hide() + self.isVisible = False + + def displayErrorOnCanvas(self): + self.rubber_band.show() + self.isVisible = True + + def extent(self) -> QgsRectangle: + return self.rubber_band.rect() + + +class QGeometryDuplicateVerticesTreeWidgetItem(QGeometryValidationTreeWidgetItem): + """ QTreeWidgetItem, dass Daten zu einem Stützpunktfehler bei der Geometrievalidierung hält""" + def __init__(self, *args, uid=None, cls_type=None, polygon=None, **kwargs): + self.error_msg = f'Planinhalt besitzt doppelte Stützpunkte' + super().__init__(*args, uid=uid, cls_type=cls_type, error_msg=self.error_msg, **kwargs) + self.polygon = QgsPolygon() + self.polygon.fromWkt(polygon) + self.rubber_band = QgsGeometryRubberBand(iface.mapCanvas(), QgsWkbTypes.PolygonGeometry) + self.rubber_band.setFillColor(QtGui.QColor(0, 0, 0, 0)) + self.rubber_band.setStrokeWidth(3) + self.rubber_band.setVertexDrawingEnabled(False) + self.rubber_band.setGeometry(self.polygon) + self.removeFromCanvas() + + def __del__(self): + iface.mapCanvas().scene().removeItem(self.rubber_band) + + def removeFromCanvas(self): + self.rubber_band.hide() + self.isVisible = False + + def displayErrorOnCanvas(self): + self.rubber_band.show() + self.isVisible = True + + def extent(self) -> QgsRectangle: + return self.rubber_band.rect() + + +class QGeometryInvalidVerticesTreeWidgetItem(QGeometryValidationTreeWidgetItem): + """ QTreeWidgetItem, dass Daten zu einem Stützpunkten enthält, die die Flächenschlussbedingung nicht erfüllen """ + def __init__(self, *args, uid=None, cls_type=None, vertices=None, **kwargs): + self.error_msg = f'Stützpunkte erfüllen den Flächenschluss nicht' + super().__init__(*args, uid=uid, cls_type=cls_type, error_msg=self.error_msg, **kwargs) + + self.vertex_markers = [] + for vertex in vertices: + marker = QgsVertexMarker(iface.mapCanvas()) + marker.setCenter(QgsPointXY(vertex[0], vertex[1])) + marker.setIconSize(5) + marker.setPenWidth(2) + marker.hide() + self.vertex_markers.append(marker) + + def __del__(self): + for marker in self.vertex_markers: + iface.mapCanvas().scene().removeItem(marker) + + def removeFromCanvas(self): + for marker in self.vertex_markers: + marker.hide() + self.isVisible = False + + def displayErrorOnCanvas(self): + for marker in self.vertex_markers: + marker.show() + self.isVisible = True + + def extent(self) -> QgsRectangle: + marker_geometries = [QgsGeometry.fromPointXY(marker.center()) for marker in self.vertex_markers] + collection = QgsGeometry.collectGeometry(marker_geometries) + return collection.boundingBox() diff --git a/src/SAGisXPlanung/gui/widgets/QCustomTreeWidgets.py b/src/SAGisXPlanung/gui/widgets/QCustomTreeWidgets.py new file mode 100644 index 0000000..5610bf2 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QCustomTreeWidgets.py @@ -0,0 +1,42 @@ +from qgis.PyQt import QtWidgets, QtCore + +from SAGisXPlanung.XPlan.feature_types import XP_Objekt +from SAGisXPlanung.XPlan.mixins import LineGeometry, PolygonGeometry, PointGeometry, MixedGeometry +from SAGisXPlanung.utils import CLASSES + + +class QObjectTypeSelectionTreeWidget(QtWidgets.QTreeWidget): + def __init__(self, *args, **kwargs): + super(QObjectTypeSelectionTreeWidget, self).__init__(*args, **kwargs) + + self.setColumnCount(1) + self.setHeaderLabel('Objektart') + + def sizeHint(self): + size = super(QObjectTypeSelectionTreeWidget, self).sizeHint() + return QtCore.QSize(size.width(), 100) + + def setup(self, cls_type): + self.clear() + plan_type_prefix = cls_type.__name__[:2] + self.iterateSubclass(CLASSES[f'{plan_type_prefix}_Objekt']) + if plan_type_prefix != 'SO': + self.iterateSubclass(CLASSES['SO_Objekt']) + + def iterateSubclass(self, cls): + for derived in cls.__subclasses__(): # type: XP_Objekt + if derived.hidden: + continue + if issubclass(derived, (LineGeometry, PolygonGeometry, PointGeometry, MixedGeometry)): + module_name = derived.__module__.split('.')[-2] + items = self.findItems(module_name, QtCore.Qt.MatchFixedString) + if items: + node = QtWidgets.QTreeWidgetItem(items[0], [derived.__name__]) + node.setIcon(0, derived.previewIcon()) + else: + top_node = QtWidgets.QTreeWidgetItem(self, [module_name]) + # noinspection PyTypeChecker + top_node.setFlags(top_node.flags() & ~QtCore.Qt.ItemIsSelectable) + node = QtWidgets.QTreeWidgetItem(top_node, [derived.__name__]) + node.setIcon(0, derived.previewIcon()) + self.iterateSubclass(derived) diff --git a/src/SAGisXPlanung/gui/widgets/QExplorerView.py b/src/SAGisXPlanung/gui/widgets/QExplorerView.py new file mode 100644 index 0000000..2e9c2d1 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QExplorerView.py @@ -0,0 +1,531 @@ +import logging +from enum import Enum + +from qgis.PyQt.QtCore import (QAbstractItemModel, Qt, QModelIndex, QPoint, QAbstractProxyModel, QSortFilterProxyModel, + pyqtSlot, QPersistentModelIndex, QItemSelection, QSize) +from qgis.PyQt.QtWidgets import (QTreeView) + +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui.style import TagStyledDelegate, HighlightRowProxyStyle, FlagNewRole + +XID_ROLE = Qt.UserRole + 2 + +logger = logging.getLogger(__name__) + + +class SortOptions(Enum): + SortHierarchy = 0 + SortAlphabet = 1 + SortCategory = 2 + + +class QExplorerView(QTreeView): + + def __init__(self, parent=None): + super(QExplorerView, self).__init__(parent) + self.model = ExplorerTreeModel() + + self.proxy = SortProxyModel(self) + self.proxy.setSourceModel(self.model) + self.setModel(self.proxy) + + self.setItemDelegate(TagStyledDelegate()) + self.proxy_style = HighlightRowProxyStyle('Fusion') + self.proxy_style.setParent(self) + self.setStyle(self.proxy_style) + self.setMouseTracking(True) + + self.sorting = SortOptions.SortHierarchy + + def sizeHint(self): + size = super(QExplorerView, self).sizeHint() + return QSize(size.width(), 80) + + def clear(self): + self.model.clear() + + def itemAtPosistion(self, position: QPoint): + index = self.indexAt(position) + item = index.model().itemAtIndex(index) + return item + + def invisibleRootItem(self): + return self.model._root + + def removeItem(self, node): + index = self.model.indexForTreeItem(node) + self.model.removeRows(node.row(), 1, index.parent()) + + def selectedItems(self): + indices = self.selectedIndexes() + return [index.model().itemAtIndex(index) for index in indices] + + def sort(self, opts: int): + if SortOptions(opts) == SortOptions.SortAlphabet: + proxy1 = FlatProxyModel() + proxy1.setSourceModel(self.model) + self.proxy.setSourceModel(proxy1) + self.proxy.sort(0, Qt.AscendingOrder) + self.sorting = SortOptions.SortAlphabet + elif SortOptions(opts) == SortOptions.SortCategory: + proxy1 = CategoryProxyModel() + proxy1.setSourceModel(self.model) + self.proxy.setSourceModel(proxy1) + self.proxy.sort(-1) + self.sorting = SortOptions.SortCategory + else: + self.proxy.setSourceModel(self.model) + self.proxy.sort(-1) + self.sorting = SortOptions.SortHierarchy + self.expandAll() + + def filter(self, filter_text): + self.proxy.setFilterRegularExpression(filter_text) + + def selectionChanged(self, selected: QItemSelection, deselected: QItemSelection): + if not deselected.indexes(): + return + index = deselected.indexes()[0] + if index.data(FlagNewRole): + while not isinstance(index.model(), ExplorerTreeModel): + index = index.model().mapToSource(index) + self.model.setData(index, False, role=FlagNewRole) + + super(QExplorerView, self).selectionChanged(selected, deselected) + + +class ExplorerTreeModel(QAbstractItemModel): + def __init__(self, nodes=None): + super(ExplorerTreeModel, self).__init__() + if nodes is None: + nodes = [] + self._horizontal_header = ['Objektbaum'] + + self._root = CustomNode() + for node in nodes: + self._root.addChild(node) + + def clear(self): + self.beginResetModel() + for i in range(self._root.childCount()): + item = self._root.child(i) + del item + self._root._children.clear() + self.endResetModel() + + def addChild(self, node, _parent=QModelIndex(), row=None): + if isinstance(_parent, ClassNode): + _parent = self.indexForTreeItem(_parent) + + if row is None: + row = self.rowCount(_parent) + if not _parent or not _parent.isValid(): + parent = self._root + else: + parent = _parent.internalPointer() + self.beginInsertRows(_parent, row, row) + parent.addChild(node, row=row) + self.endInsertRows() + + def removeRows(self, row: int, count: int, parent: QModelIndex = None) -> bool: + self.beginRemoveRows(parent, row, row + count - 1) + parent_item = self.itemAtIndex(parent) + for i in range(count): + parent_item.removeChild(row + i) + self.endRemoveRows() + return True + + def data(self, index, role=Qt.DisplayRole): + if not index.isValid(): + return None + node = index.internalPointer() + if role == Qt.DisplayRole: + return node.data(index.column()) + if role == FlagNewRole: + return node.flag_new + if role == XID_ROLE and isinstance(node, ClassNode): + return node.xplanItem().xid + return None + + def setData(self, index: QModelIndex, value, role=Qt.EditRole): + if role == FlagNewRole: + node = index.internalPointer() + if not node: + return + node.flag_new = value + self.dataChanged.emit(index, index) + + def headerData(self, col, orientation, role=Qt.DisplayRole): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self._horizontal_header[col] + + def indexForTreeItem(self, node): + index = self.createIndex(node.row(), 0, node) + return index + + def itemAtIndex(self, index: QModelIndex): + if not index.isValid(): + return self._root + + return index.internalPointer() + + def index(self, row, column, _parent=QModelIndex()): + if _parent.isValid() and _parent.column() != 0: + return QModelIndex() + + parentItem = self.itemAtIndex(_parent) + child = parentItem.child(row) + if child: + return self.createIndex(row, column, child) + else: + return QModelIndex() + + def parent(self, index): + if not index.isValid(): + return QModelIndex() + + child = self.itemAtIndex(index) + parentItem = child.parent() + if parentItem == self._root: + return QModelIndex() + + return self.createIndex(parentItem.row(), 0, parentItem) + + def rowCount(self, index): + if index.isValid(): + return index.internalPointer().childCount() + return self._root.childCount() + + def columnCount(self, index): + return 1 + + +class FlatProxyModel(QAbstractProxyModel): + + def __init__(self): + self.index_lookup = [] + self.index_lookup_proxy = [] + + super(FlatProxyModel, self).__init__() + + def setSourceModel(self, model: QAbstractItemModel): + self.buildSourceMap(model) + super(FlatProxyModel, self).setSourceModel(model) + + self.sourceModel().rowsRemoved.connect(self.onRowsChanged) + self.sourceModel().rowsInserted.connect(self.onRowsChanged) + self.sourceModel().dataChanged.connect(self.sourceDataChanged) + + @pyqtSlot(QModelIndex, QModelIndex) + def sourceDataChanged(self, top_left, bottom_right): + self.dataChanged.emit(self.mapFromSource(top_left), self.mapFromSource(bottom_right)) + + @pyqtSlot(QModelIndex, int, int) + def onRowsChanged(self, parent: QModelIndex, first: int, last: int): + self.layoutAboutToBeChanged.emit() + self.index_lookup = [] + self.index_lookup_proxy = [] + self.buildSourceMap(self.sourceModel()) + self.layoutChanged.emit() + + def buildSourceMap(self, model, parent=QModelIndex()): + rows = model.rowCount(parent) + for r in range(rows): + index = model.index(r, 0, parent) + + proxy_index = QPersistentModelIndex(self.createIndex(len(self.index_lookup), 0, index.internalPointer())) + self.index_lookup.append(QPersistentModelIndex(index)) + self.index_lookup_proxy.append(proxy_index) + + if model.itemAtIndex(index).childCount() > 0: + self.buildSourceMap(model, index) + + def itemAtIndex(self, index: QModelIndex): + if not self.sourceModel(): + return + index = self.mapToSource(index) + return self.sourceModel().itemAtIndex(index) + + def mapFromSource(self, source_index: QModelIndex): + if not source_index.isValid() or QPersistentModelIndex(source_index) not in self.index_lookup: + return QModelIndex() + row = self.index_lookup.index(QPersistentModelIndex(source_index)) + proxy_index = self.index_lookup_proxy[row] + return QModelIndex(proxy_index) + + def mapToSource(self, proxy_index: QModelIndex): + if not proxy_index.isValid() or proxy_index.row() > len(self.index_lookup) - 1: + return QModelIndex() + + return QModelIndex(self.index_lookup[proxy_index.row()]) + + def index(self, row, column, parent=QModelIndex()): + if parent.isValid(): + return QModelIndex() + + source_index = QModelIndex(self.index_lookup[row]) + return self.createIndex(row, column, source_index.internalPointer()) + + def parent(self, index): + return QModelIndex() + + def hasChildren(self, index=QModelIndex()): + return self.rowCount(index) > 0 + + def rowCount(self, index): + if not index.isValid(): + return len(self.index_lookup) + return 0 + + def columnCount(self, index): + return 1 + + +class CategoryProxyModel(QAbstractProxyModel): + + def __init__(self): + self.categories = [] + self.category_model_indices = {} + self.category_items = {} + self.category_items_proxy = {} + + super(CategoryProxyModel, self).__init__() + + def setSourceModel(self, model: QAbstractItemModel): + self.buildSourceMap(model) + for i, cat in enumerate(self.categories): + self.category_model_indices[cat] = QPersistentModelIndex(self.createIndex(i, 0)) + super(CategoryProxyModel, self).setSourceModel(model) + + self.sourceModel().rowsRemoved.connect(self.onRowsChanged) + self.sourceModel().rowsInserted.connect(self.onRowsChanged) + self.sourceModel().dataChanged.connect(self.sourceDataChanged) + + @pyqtSlot(QModelIndex, QModelIndex) + def sourceDataChanged(self, top_left, bottom_right): + self.dataChanged.emit(self.mapFromSource(top_left), self.mapFromSource(bottom_right)) + + @pyqtSlot(QModelIndex, int, int) + def onRowsChanged(self, parent: QModelIndex, first: int, last: int): + self.layoutAboutToBeChanged.emit() + self.categories = [] + self.category_model_indices = {} + self.category_items = {} + self.category_items_proxy = {} + self.buildSourceMap(self.sourceModel()) + for i, cat in enumerate(self.categories): + self.category_model_indices[cat] = QPersistentModelIndex(self.createIndex(i, 0)) + self.layoutChanged.emit() + + def buildSourceMap(self, model, parent=QModelIndex()): + rows = model.rowCount(parent) + for r in range(rows): + index = model.index(r, 0, parent) + + item = model.itemAtIndex(index) + if item.xplan_module not in self.categories: + self.categories.append(item.xplan_module) + self.category_items[item.xplan_module] = [QPersistentModelIndex(index)] + new_index = QPersistentModelIndex(self.createIndex(0, 0, index.internalPointer())) + self.category_items_proxy[item.xplan_module] = [new_index] + else: + self.category_items[item.xplan_module].append(QPersistentModelIndex(index)) + prev_row = len(self.category_items_proxy[item.xplan_module]) + new_index = QPersistentModelIndex(self.createIndex(prev_row, 0, index.internalPointer())) + self.category_items_proxy[item.xplan_module].append(new_index) + + if item.childCount() > 0: + self.buildSourceMap(model, index) + + def itemAtIndex(self, index: QModelIndex): + if not self.sourceModel(): + return + index = self.mapToSource(index) + return self.sourceModel().itemAtIndex(index) + + def data(self, index: QModelIndex, role=Qt.DisplayRole): + if not index.parent().isValid() and index.row() < len(self.categories): + if role == Qt.DisplayRole: + return self.categories[index.row()] + + return self.sourceModel().data(self.mapToSource(index), role) + + def mapFromSource(self, source_index: QModelIndex): + if not source_index.isValid(): + return QModelIndex() + + source_item = source_index.internalPointer() + category = source_item.xplan_module + persistent_index = QPersistentModelIndex(source_index) + row = self.category_items[category].index(persistent_index) + proxy_index = self.category_items_proxy[category][row] + + return QModelIndex(proxy_index) + + def mapToSource(self, proxy_index: QModelIndex): + if not proxy_index.isValid(): + return QModelIndex() + + proxy_item = proxy_index.internalPointer() + if not proxy_item: + return QModelIndex() + category = proxy_item.xplan_module + persistent_index = QPersistentModelIndex(proxy_index) + row = self.category_items_proxy[category].index(persistent_index) + source_index = self.category_items[category][row] + + return QModelIndex(source_index) + + def index(self, row: int, column: int, parent=QModelIndex()): + if not parent.isValid(): + return self.createIndex(row, column) + + # get category from category persistent index (parent) + category = next((cat for cat, index in self.category_model_indices.items() + if index == QPersistentModelIndex(parent)), None) + + if category is None: + return QModelIndex() + + source_index = QModelIndex(self.category_items[category][row]) + return self.createIndex(row, column, source_index.internalPointer()) + + def parent(self, index: QModelIndex = None): + # if category node was found, source index is invalid + if not index.isValid() or index.internalPointer() is None: + return QModelIndex() + + item = index.internalPointer() + category = item.xplan_module + category_persistent_index = self.category_model_indices[category] + return QModelIndex(category_persistent_index) + + def hasChildren(self, index=QModelIndex()): + return self.rowCount(index) > 0 + + def rowCount(self, index): + if not index.isValid(): + return len(self.categories) + + if index.parent().isValid(): + return 0 + + category_row = index.row() + category = self.categories[category_row] + count = len(self.category_items[category]) + return count + + def columnCount(self, index): + return 1 + + +class SortProxyModel(QSortFilterProxyModel): + def __init__(self, parent=None): + super().__init__(parent) + + self.setRecursiveFilteringEnabled(True) + + def itemAtIndex(self, index: QModelIndex): + if not self.sourceModel(): + return + index = self.mapToSource(index) + return self.sourceModel().itemAtIndex(index) + + def setSourceModel(self, model: QAbstractItemModel): + super(SortProxyModel, self).setSourceModel(model) + + self.sourceModel().dataChanged.connect(lambda index1, index2: self.invalidateFilter()) + self.sourceModel().rowsInserted.connect(lambda p, f, l: self.invalidate()) + + def filterAcceptsRow(self, source_row: int, parent: QModelIndex): + index = self.sourceModel().index(source_row, 0, parent) + if not index.isValid(): + return True + + # dont filter for category nodes/ invalid nodes + if index.internalPointer() is None: + return False + return self.filterRegularExpression().pattern().lower() in str(self.sourceModel().data(index)).lower() or \ + self.filterRegularExpression().pattern().lower() in str(index.internalPointer().id()).lower() + + +# ****************** NODES **************************** + + +class CustomNode: + def __init__(self, new=False): + self._children = [] + self._parent = None + self._row = 0 + + self.flag_new = new + self.column_count = 1 + + def data(self, column): + pass + + def id(self): + pass + + def childCount(self): + return len(self._children) + + def child(self, row): + if 0 <= row < self.childCount(): + return self._children[row] + + def parent(self): + return self._parent + + def row(self): + return self._row + + def addChild(self, child, row=None): + child._parent = self + if row is not None: + child._row = row + self._children.insert(row, child) + + # update child rows to reflect changes + for i in range(len(self._children)): + self.child(i)._row = i + else: + child._row = len(self._children) + self._children.append(child) + + def removeChild(self, row): + if row < 0 or row > len(self._children): + return + child = self._children.pop(row) + child._parent = None + + # update child rows to reflect changes + for i in range(len(self._children)): + c = self.child(i)._row = i + + +class ClassNode(CustomNode): + def __init__(self, data: XPlanungItem, new=False): + super(ClassNode, self).__init__(new=new) + self._data = data + + self.xplan_module = self._data.xtype.__module__.split('.')[-2] + + def data(self, column): + return self._data.xtype.__name__ + + def id(self): + return self._data.xid + + def xplanItem(self): + return self._data + + +class CategoryNode(CustomNode): + def __init__(self, category_name): + self.category_name = category_name + + super(CategoryNode, self).__init__() + + def data(self, column): + return self.category_name diff --git a/src/SAGisXPlanung/gui/widgets/QFeatureIdentify.py b/src/SAGisXPlanung/gui/widgets/QFeatureIdentify.py new file mode 100644 index 0000000..3e1f06c --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QFeatureIdentify.py @@ -0,0 +1,210 @@ +import logging +from typing import Union + +from geoalchemy2 import WKTElement +from qgis.PyQt.QtWidgets import QSizePolicy, QLabel, QCheckBox +from qgis.PyQt import QtWidgets, QtCore +from qgis.gui import QgsMapLayerComboBox, QgsFeaturePickerWidget +from qgis.core import QgsMapLayerProxyModel, QgsGeometry, QgsFeature +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtCore import pyqtSlot, QEvent +from qgis.utils import iface + +from SAGisXPlanung.GML.geometry import geometry_from_spatial_element +from SAGisXPlanung.Tools.IdentifyFeatureTool import IdentifyFeatureTool +from shapely.geometry import MultiPolygon +from shapely.wkt import loads + +from SAGisXPlanung.gui.widgets import ElideLabel +from SAGisXPlanung.gui.widgets.QXPlanInputElement import XPlanungInputMeta, QXPlanInputElement + +logger = logging.getLogger(__name__) + + +class QFeatureIdentify(QXPlanInputElement, QtWidgets.QWidget, metaclass=XPlanungInputMeta): + """ + Widget zum Auswählen von Features eines Polygon-VektorLayers durch Klick auf den Karten-Canvas + """ + def __init__(self, geometry=None, *args, **kwargs): + super(QFeatureIdentify, self).__init__(*args, **kwargs) + self.geometry = geometry + + self.verticalLayout = QtWidgets.QVBoxLayout(self) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout = QtWidgets.QHBoxLayout(self) + self.horizontalLayout.setSpacing(20) + + self.reconfigure_layout = QtWidgets.QHBoxLayout(self) + self.geometry_text = ElideLabel() + self.geometry_text.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.geometry_text.setStyleSheet('color: #4b5563') + self.edit_enabled = QCheckBox('Geometrie anpassen') + self.reconfigure_layout.addWidget(self.geometry_text) + self.reconfigure_layout.addWidget(self.edit_enabled) + if self.geometry: + geom = geometry_from_spatial_element(self.geometry) + self.geometry_text.setText(geom.asWkt()) + self.verticalLayout.addLayout(self.reconfigure_layout) + + self.geometry_layout = QtWidgets.QVBoxLayout(self) + self.mMapLayerComboBox = QgsMapLayerComboBox(self) + self.mMapLayerComboBox.setFilters(QgsMapLayerProxyModel.PolygonLayer) + self.geometry_layout.addWidget(self.mMapLayerComboBox) + + self.layer = self.mMapLayerComboBox.currentLayer() + self.featureGeometry: Union[QgsGeometry, None] = None + + self.horizontalLayout.addItem(QtWidgets.QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) + + self.label = QtWidgets.QLabel("Feature:") + self.label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) + self.horizontalLayout.addWidget(self.label) + self.cbFeature = QgsFeaturePickerWidget(self) + self.cbFeature.setMaximumWidth(self.cbFeature.sizeHint().width() + 40) + self.cbFeature.setLayer(self.layer) + self.cbFeature.featureChanged.connect(self.onFeatureChanged) + self.cbFeature.setAllowNull(True) + self.horizontalLayout.addWidget(self.cbFeature) + + self.bIdentify = QtWidgets.QPushButton(self) + self.bIdentify.setText("") + self.bIdentify.setToolTip("Geltungsbereich auf Karte wählen") + self.bIdentify.setIcon(QIcon(':/images/themes/default/mActionIdentifyByPolygon.svg')) + self.bIdentify.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) + self.bIdentify.clicked.connect(self.identifyFeature) + self.horizontalLayout.addWidget(self.bIdentify) + + self.geometry_layout.addLayout(self.horizontalLayout) + self.verticalLayout.addLayout(self.geometry_layout) + + self.mapTool = IdentifyFeatureTool(iface.mapCanvas()) + self.mapTool.setLayer(self.layer) + self.mapTool.featureIdentified.connect(self.onFeatureIdentified) + self.mapTool.noFeatureSelected.connect(self.noFeatureSelected) + self.mMapLayerComboBox.layerChanged.connect(self.onLayerChanged) + + self.bIdentify.setEnabled(self.mMapLayerComboBox.count() != 0) + self.cbFeature.setEnabled(self.mMapLayerComboBox.count() != 0) + self.edit_enabled.stateChanged.connect(self.onEditEnabledCheck) + + self.installEventFilter(self) + + def eventFilter(self, obj, event): + """ Wird benötigt, damit der QgsFeaturePicker nicht größer wächst als das Fenster. + Führt zu leichtem flickern, wenn sich die Größe des Fensters verändert, daher keine optimale Lösung. + + Korrekte Lösung wäre die Nutzung von SizePolicy's der zugrundeliegenden ComboBox, diese ist aber als + privates Attribut `mComboBox` der Klasse `QgsFeaturePickerWidget` versteckt.""" + if event.type() == QEvent.Resize: + self.cbFeature.setMaximumWidth(self.mMapLayerComboBox.width() / 2) + return True + return False + + @pyqtSlot(int) + def onEditEnabledCheck(self, state: int): + self.mMapLayerComboBox.setEnabled(state) + self.label.setEnabled(state) + self.bIdentify.setEnabled(state and self.mMapLayerComboBox.count() != 0) + self.cbFeature.setEnabled(state and self.mMapLayerComboBox.count() != 0) + if state == 0: + geom = geometry_from_spatial_element(self.geometry) + self.geometry_text.setText(geom.asWkt()) + else: + self.geometry_text.setText(self.featureGeometry.asWkt()) + + def value(self): + try: + if not self.edit_enabled.isChecked() and self.geometry: + return self.geometry + wkt = WKTElement(self.geom().asWkt(), srid=self.srid) + return wkt + except AttributeError as e: + logger.warning(e) + + def setDefault(self, default): + self.geometry = default + geom = geometry_from_spatial_element(self.geometry) + self.geometry_text.setText(geom.asWkt()) + self.verticalLayout.insertLayout(0, self.reconfigure_layout) + self.onEditEnabledCheck(0) + + @property + def srid(self) -> int: + srid = self.layer.crs().postgisSrid() + return srid + + def geom(self): + self.layer.removeSelection() + return self.featureGeometry + + def onFeatureIdentified(self, feature): + self.cbFeature.setFeature(feature.id()) + self.setInvalid(False) + + def noFeatureSelected(self): + null_index = self.cbFeature.nullIndex() + self.cbFeature.setFeature(None) + # self.cbFeature.mComboBox.setCurrentIndex(null_index) # TODO: return combobox index to null value ? + + @pyqtSlot() + def identifyFeature(self): + iface.mapCanvas().setMapTool(self.mapTool) + + @pyqtSlot() + def onLayerChanged(self): + self.bIdentify.setEnabled(self.mMapLayerComboBox.count() != 0) + self.cbFeature.setEnabled(self.mMapLayerComboBox.count() != 0) + + self.layer.removeSelection() if self.layer is not None else 0 + self.layer = self.mMapLayerComboBox.currentLayer() + self.mapTool.setLayer(self.layer) + self.cbFeature.setLayer(self.layer) + self.setInvalid(False) + + def onFeatureChanged(self, feat: QgsFeature): + self.layer.removeSelection() + self.layer.select([feat.id()]) + self.featureGeometry = feat.geometry() + self.setInvalid(False) + if self.featureGeometry: + self.geometry_text.setText(self.featureGeometry.asWkt()) + + def validate_widget(self, required): + if self.geometry and not self.edit_enabled.isChecked(): + return True + feat_id = self.cbFeature.feature().id() + if feat_id >= 0: + return True + self.setInvalid(True) + return False + + def setInvalid(self, is_invalid): + if not is_invalid: + self.setStyleSheet('') + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + return + self.setAttribute(QtCore.Qt.WA_StyledBackground, True) + self.verticalLayout.setContentsMargins(5, 5, 5, 5) + self.setStyleSheet('QFeatureIdentify {background-color: #ffb0b0; border: 1px solid red; border-radius:5px;}') + + +def load_geometry(feat): + """ + Konvertiert Geometrie eines QGIS Polygon-Vektorlayer Features in eine entsprechende shapely-Geometrie. + + Parameters + ---------- + feat: qgis.core.QgsFeature + Polygon-Vektorlayer + Returns + ------- + shapely.geometry.MultiPolygon + """ + if not feat.geometry(): + return + geom = feat.geometry() + geom.convertToStraightSegment() + feature = loads(geom.asWkt()) + if feature.geom_type == 'MultiPolygon': + return feature + return MultiPolygon([feature]) diff --git a/src/SAGisXPlanung/gui/widgets/QPlanAssignmentWidget.py b/src/SAGisXPlanung/gui/widgets/QPlanAssignmentWidget.py new file mode 100644 index 0000000..b5fe597 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QPlanAssignmentWidget.py @@ -0,0 +1,152 @@ +import logging +import uuid + +from qgis.core import QgsGeometry +from qgis.PyQt import QtWidgets +from qgis.PyQt.QtCore import pyqtSignal +from qgis.PyQt.QtWidgets import QHBoxLayout, QLabel, QSpacerItem, QSizePolicy +from sqlalchemy.orm import load_only + +from SAGisXPlanung import Session +from SAGisXPlanung.XPlan.enums import XP_BedeutungenBereich +from SAGisXPlanung.XPlan.feature_types import XP_Plan +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui.widgets.QPlanComboBox import QPlanComboBox +from SAGisXPlanung.gui.widgets.QXPlanInputElement import QComboBoxNoScroll + +logger = logging.getLogger(__name__) + + +class QPlanAssignmentWidget(QtWidgets.QGroupBox): + """ Widget zur Auswahl eines Plans und zugehörigen Bereich """ + + addedBereich = pyqtSignal(XPlanungItem) + planChanged = pyqtSignal() + + def __init__(self, *args, **kwargs): + super(QPlanAssignmentWidget, self).__init__(*args, **kwargs) + + self.selectedPlan = None + self.plan_id = None + self.plan_cls_type = None + self.bereich_ids = [] + self.geom = None + + self.setTitle('Zuordnung zu einem erfassten Plan') + self.verticalLayout = QtWidgets.QVBoxLayout() + + self.hl1 = QtWidgets.QHBoxLayout() + label1 = QtWidgets.QLabel('Plan') + label1.setFixedWidth(60) + self.cbPlaene = QPlanComboBox() + self.cbPlaene.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + self.hl1.addWidget(label1) + self.hl1.addWidget(self.cbPlaene) + + self.hl2 = QtWidgets.QHBoxLayout() + label2 = QtWidgets.QLabel('Bereich') + label2.setFixedWidth(60) + self.cbBereich = QComboBoxNoScroll() + self.cbBereich.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + self.hl2.addWidget(label2) + self.hl2.addWidget(self.cbBereich) + + self.verticalLayout.addLayout(self.hl1) + self.verticalLayout.addLayout(self.hl2) + + self.setLayout(self.verticalLayout) + + self.cbPlaene.currentIndexChanged.connect(lambda i: self.onPlanChanged(i)) + if self.cbPlaene.currentIndex() != -1: + self.onPlanChanged(self.cbPlaene.currentIndex()) + + def setCurrentSelection(self, xid: str): + self.cbPlaene.setCurrentPlan(xid) + + def onPlanChanged(self, i): + self.validate_geometry() + with Session.begin() as session: + self.selectedPlan = session.query(XP_Plan).options( + load_only('id'), + ).get(self.cbPlaene.currentPlanId()) + names = [f'Bereich {bereich.nummer} - {bereich.name}' for bereich in self.selectedPlan.bereich] + self.bereich_ids = [bereich.id for bereich in self.selectedPlan.bereich] + self.plan_id = self.selectedPlan.id + self.plan_cls_type = self.selectedPlan.__class__ + + self.cbBereich.clear() + self.cbBereich.addItems(names) + + self.planChanged.emit() + + def bereich_id(self): + return self.bereich_ids[self.cbBereich.currentIndex()] + + def removeErrorMessage(self): + if self.verticalLayout.count() < 3: + return + item = self.verticalLayout.takeAt(2) + while item.count(): + child = item.takeAt(0) + if child.widget(): + child.widget().deleteLater() + + def validate_geometry(self, geom: QgsGeometry = None): + if not geom and not self.geom: + return + elif geom: + self.geom = geom + + with Session.begin() as session: + plan: XP_Plan = session.query(XP_Plan).get(self.cbPlaene.currentPlanId()) + valid = self.geom.within(plan.geometry()) + + self.removeErrorMessage() + if valid: + return + + hbox = QHBoxLayout() + hbox.addItem((QSpacerItem(70, 10, QSizePolicy.Fixed, QSizePolicy.Expanding))) + label = QLabel('Ausgewählte Geometrie liegt nicht im Geltungsbereich des gewählten Planwerks!') + label.setStyleSheet("color: #F59E0B") + hbox.addWidget(label) + self.verticalLayout.addLayout(hbox) + + def validate_assignment(self): + if self.cbBereich.currentIndex() != -1: + return True + + msg = QtWidgets.QMessageBox() + msg.setIcon(QtWidgets.QMessageBox.Warning) + + msg.setText(f"Das ausgewählte Planwerk enthält keinen Bereich. Soll ein Standardbereich automatisch erstellt" + f"werden, um die Zuordnung auszuführen?") + msg.setWindowTitle("Fehlender XP_Bereich") + msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) + ret = msg.exec_() + if ret == QtWidgets.QMessageBox.No: + return False + + self.addDefaultXP_Bereich() + return True + + def addDefaultXP_Bereich(self): + with Session.begin() as session: + self.selectedPlan = session.query(XP_Plan).get(self.cbPlaene.currentPlanId()) + cls_type = getattr(self.selectedPlan.__class__, 'bereich').property.entity.class_ + bereich_obj = cls_type() + bereich_obj.id = uuid.uuid4() + bereich_obj.name = 'Geltungsbereich' + bereich_obj.nummer = '0' + bereich_obj.bedeutung = XP_BedeutungenBereich.Teilbereich + bereich_obj.geltungsbereich = self.selectedPlan.raeumlicherGeltungsbereich + self.selectedPlan.bereich.append(bereich_obj) + + self.bereich_ids.append(bereich_obj.id) + self.cbBereich.addItems([f'Bereich {bereich_obj.nummer} - {bereich_obj.name}']) + self.cbBereich.setCurrentIndex(0) + + self.addedBereich.emit(XPlanungItem(xid=str(bereich_obj.id), xtype=cls_type, + plan_xid=str(self.cbPlaene.currentPlanId()))) + + diff --git a/src/SAGisXPlanung/gui/widgets/QPlanComboBox.py b/src/SAGisXPlanung/gui/widgets/QPlanComboBox.py new file mode 100644 index 0000000..311bdd6 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QPlanComboBox.py @@ -0,0 +1,61 @@ +import logging + +from qgis.PyQt.QtGui import QPaintEvent +from qgis.PyQt.QtWidgets import QStyleOptionComboBox, QStylePainter, QStyle, QComboBox +from qgis.PyQt.QtCore import Qt +from sqlalchemy.orm import load_only + +from SAGisXPlanung import Session +from SAGisXPlanung.XPlan.feature_types import XP_Plan +from SAGisXPlanung.gui.widgets.QXPlanInputElement import QComboBoxNoScroll + +logger = logging.getLogger(__name__) + + +class QPlanComboBox(QComboBoxNoScroll): + """ Combobox zur Auswahl eines in der Datenbank erfassten Plans """ + def __init__(self, *args, **kwargs): + super(QPlanComboBox, self).__init__(*args, **kwargs) + + self.setMinimumContentsLength(20) + self.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon) + + self.refresh() + + def refresh(self): + """ Aktualisiert die Combobox mit den aktuell gespeicherten Plänen """ + try: + self.clear() + with Session.begin() as session: + plans = session.query(XP_Plan).options( + load_only(XP_Plan.id, XP_Plan.name, XP_Plan.type) + ).order_by(XP_Plan.name).all() + for plan in plans: + self.addItem(f'{plan.name} ({plan.type})', str(plan.id)) + except Exception as e: + logger.exception(e) + + def setCurrentPlan(self, xid: str): + index = self.findData(xid) + if index >= 0: + self.setCurrentIndex(index) + + def setPlanName(self, xid: str, name: str): + index = self.findData(xid) + if index >= 0: + prev_name = self.itemText(index) + self.setItemText(index, f'{name} {prev_name[prev_name.find("("):]}') + + def currentPlanId(self): + return self.currentData() + + def paintEvent(self, e: QPaintEvent): + opt = QStyleOptionComboBox() + self.initStyleOption(opt) + + p = QStylePainter(self) + p.drawComplexControl(QStyle.CC_ComboBox, opt) + + text_rect = self.style().subControlRect(QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxEditField) + opt.currentText = p.fontMetrics().elidedText(opt.currentText, Qt.ElideRight, text_rect.width()) + p.drawControl(QStyle.CE_ComboBoxLabel, opt) \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/widgets/QRelationDropdowns.py b/src/SAGisXPlanung/gui/widgets/QRelationDropdowns.py new file mode 100644 index 0000000..9fbe735 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QRelationDropdowns.py @@ -0,0 +1,179 @@ +import logging +import os + +from qgis.PyQt.QtGui import QIcon, QPixmap, QPainter, QColor +from qgis.PyQt.QtWidgets import QAction, QWidget, QHBoxLayout, QToolButton, QMenu, QMessageBox, QDialog +from qgis.PyQt.QtCore import pyqtSlot, Qt, QSize, QEvent +from qgis.gui import QgsCheckableComboBox +from qgis.utils import iface + +from SAGisXPlanung import Session, BASE_DIR +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui.widgets.QXPlanInputElement import QComboBoxNoScroll, QXPlanInputElement, XPlanungInputMeta +from SAGisXPlanung.utils import confirmObjectDeletion + +logger = logging.getLogger(__name__) + + +class QAddRelationDropdown(QWidget, QXPlanInputElement, metaclass=XPlanungInputMeta): + + def __init__(self, parent, relation, *args, **kwargs): + super(QAddRelationDropdown, self).__init__(*args, **kwargs) + self.relation = relation + self.cls = self.relation[1].mapper.class_ + self.requires_relation = bool(self.relation[1].secondary is not None) + self.parent = parent + + self.layout = QHBoxLayout() + self.layout.setContentsMargins(0, 0, 0, 0) + self.container = QWidget() + self.container.setObjectName('container') + self.container.setLayout(QHBoxLayout()) + self.container.layout().setContentsMargins(0, 0, 0, 0) + + if self.requires_relation: + self.cb = QgsCheckableComboBox() + # this is not emitted on QGIS 3.16, see https://github.com/qgis/QGIS/pull/43487 for infos, + self.cb.checkedItemsChanged.connect(lambda items: self.setInvalid(not bool(items))) + self.cb.view().customContextMenuRequested.disconnect() + else: + self.cb = QComboBoxNoScroll() + self.cb.setFocusPolicy(Qt.StrongFocus) + + self.refreshComboBox() + + self.plus_icon_path = os.path.abspath(os.path.join(BASE_DIR, 'gui/resources/plus.svg')) + self.b_plus = QToolButton() + self.b_plus.setIcon(self.loadSvg(self.plus_icon_path)) + self.b_plus.installEventFilter(self) + self.b_plus.setCursor(Qt.PointingHandCursor) + self.b_plus.setToolTip('Neues Objekt hinzufügen') + self.b_plus.clicked.connect(self.addRelation) + self.b_plus.setStyleSheet(''' + QToolButton { + background: palette(window); + border: 0px; + } + ''') + + self.cb.view().setContextMenuPolicy(Qt.CustomContextMenu) + self.cb.view().customContextMenuRequested.connect(self.onContextMenuRequested) + + self.container.layout().addWidget(self.cb) + self.layout.addWidget(self.container) + self.layout.addWidget(self.b_plus) + self.setLayout(self.layout) + + def onContextMenuRequested(self, point): + index = self.cb.view().indexAt(point) + if not index.isValid(): + return + + data = self.cb.model().data(index, Qt.UserRole) + if not data: + return + + menu = QMenu() + + edit_action = QAction(QIcon(':/images/themes/default/mActionMultiEdit.svg'), 'Bearbeiten') + edit_action.triggered.connect(lambda s, item_data=data: self.onEditObject(item_data)) + menu.addAction(edit_action) + + delete_action = QAction(QIcon(os.path.join(BASE_DIR, 'gui/resources/delete.svg')), 'Löschen') + delete_action.triggered.connect(lambda s, item_data=data: self.onDeleteObject(item_data)) + menu.addAction(delete_action) + + menu.exec_(self.cb.view().viewport().mapToGlobal(point)) + + def onEditObject(self, item_xid): + from SAGisXPlanung.gui.XPEditObjectDialog import XPEditObjectDialog + + xplan_item = XPlanungItem(xtype=self.cls, xid=item_xid) + + d = XPEditObjectDialog(xplan_item) + result = d.exec_() + if result == QDialog.Accepted: + self.refreshComboBox() + + def onDeleteObject(self, item_xid): + with Session.begin() as session: + obj_from_db = session.query(self.cls).get(item_xid) + + if not confirmObjectDeletion(obj_from_db): + return + + session.delete(obj_from_db) + self.refreshComboBox() + + def eventFilter(self, obj, event): + if event.type() == QEvent.HoverEnter: + obj.setIcon(self.loadSvg(obj.icon().pixmap(obj.icon().actualSize(QSize(32, 32))), color='#1F2937')) + elif event.type() == QEvent.HoverLeave: + obj.setIcon(self.loadSvg(obj.icon().pixmap(obj.icon().actualSize(QSize(32, 32))), color='#6B7280')) + return False + + def loadSvg(self, svg, color=None): + img = QPixmap(svg) + if color: + qp = QPainter(img) + qp.setCompositionMode(QPainter.CompositionMode_SourceIn) + qp.fillRect(img.rect(), QColor(color)) + qp.end() + return QIcon(img) + + @pyqtSlot() + def addRelation(self): + from SAGisXPlanung.gui.XPCreatePlanDialog import XPCreatePlanDialog + d = XPCreatePlanDialog(iface=iface, cls_type=self.cls, parent_type=self.parent.cls_type) + d.contentSaved.connect(self.refreshComboBox) + d.exec_() + + def refreshComboBox(self): + self.cb.clear() + if not isinstance(self.cb, QgsCheckableComboBox): + self.cb.addItem("") + with Session.begin() as session: + for r in session.query(self.cls).all(): + self.cb.addItem(str(r), str(r.id)) + self.cb.model().sort(0) + + def validate_widget(self, required): + if not self.requires_relation: + return True + + if not self.cb.checkedItems(): + self.setInvalid(True) + return False + + self.setInvalid(False) + return True + + def setInvalid(self, is_invalid): + if not is_invalid: + self.container.setStyleSheet('') + self.container.layout().setContentsMargins(0, 0, 0, 0) + return + self.container.setAttribute(Qt.WA_StyledBackground, True) + self.container.layout().setContentsMargins(5, 5, 5, 5) + self.container.setStyleSheet('#container {background-color: #ffb0b0; border: 1px solid red; border-radius: 3px;}') + + def value(self): + values = [] + + if not isinstance(self.cb, QgsCheckableComboBox): + with Session.begin() as session: + return session.get(self.cls, self.cb.currentData()) + + user_data = self.cb.checkedItemsData() + with Session.begin() as session: + for i, item in enumerate(self.cb.checkedItems()): + values.append(session.get(self.cls, user_data[i])) + + return values + + def setDefault(self, default): + if isinstance(self.cb, QgsCheckableComboBox): + self.cb.setCheckedItems([str(o) for o in default]) + else: + index = self.cb.findData(str(default.id)) + self.cb.setCurrentIndex(index) diff --git a/src/SAGisXPlanung/gui/widgets/QXPlanInputElement.py b/src/SAGisXPlanung/gui/widgets/QXPlanInputElement.py new file mode 100644 index 0000000..f6dd448 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QXPlanInputElement.py @@ -0,0 +1,580 @@ +import os +import re +import sys +import datetime +from abc import ABC, abstractmethod +from pathlib import Path + +from geoalchemy2 import Geometry +from qgis.PyQt.QtGui import QIcon, QPixmap, QPainter, QColor +from qgis.PyQt.QtWidgets import (QLineEdit, QComboBox, QRadioButton, QTextEdit, QToolButton, QWidget, + QHBoxLayout, QVBoxLayout, QSpacerItem, QSizePolicy, QPushButton, QButtonGroup, QLabel) +from qgis.PyQt.QtCore import Qt, QDate, QObject, pyqtSlot, QEvent, QSize, QVersionNumber, qVersion +from qgis.gui import QgsCheckableComboBox, QgsFileWidget, QgsDateEdit +from sqlalchemy import ARRAY + +from SAGisXPlanung import BASE_DIR +from SAGisXPlanung.config import export_version +from SAGisXPlanung.utils import is_url +from SAGisXPlanung.XPlan.types import LargeString, RefURL, Angle, Length, Volume, Area, Scale, XPlanungMeasureType, \ + RegExString, XPEnum + +PYQT_DEFAULT_DATE = datetime.date(1752, 9, 14) + + +class QXPlanInputElement(ABC): + """ Abstrakte Basisklasse für alle Eingabeformulare. + Mit der Factory-Methode `create` kann ein zum Datentyp passendes Eingabefeld genereriert werden""" + + background_widget = None + invalid = False + error_message = None + + @abstractmethod + def value(self): + pass + + @abstractmethod + def setDefault(self, default): + pass + + @abstractmethod + def validate_widget(self, required): + pass + + def setInvalid(self, set_invalid): + if set_invalid: + self.insert_background_widget() + self.background_widget.setStyleSheet( + 'QWidget#back {background-color: #ffb0b0; border: 1px solid red; border-radius: 3px;}') + self.invalid = True + else: + self.remove_background_widget() + self.background_widget.setStyleSheet('') + self.invalid = False + + def error_message_label(self) -> QLabel: + error_label = QLabel(self.error_message) + error_label.setWordWrap(True) + error_label.setStyleSheet("font-weight: bold; font-size: 7pt; color: #991B1B") + return error_label + + def insert_background_widget(self): + if self.invalid: + return + + self.background_widget = QWidget() + parent = self.parentWidget().layout() + parent.replaceWidget(self, self.background_widget) + + vbox = QVBoxLayout() + vbox.setSpacing(3) + vbox.addWidget(self) + if self.error_message: + vbox.addWidget(self.error_message_label()) + + self.background_widget.setLayout(vbox) + self.background_widget.setObjectName("back") + self.background_widget.setAttribute(Qt.WA_StyledBackground, True) + self.background_widget.layout().setContentsMargins(5, 5, 5, 5) + + def remove_background_widget(self): + if not self.invalid: + return + + layout = self.parentWidget().parentWidget().layout() + layout.replaceWidget(self.background_widget, self) + self.background_widget.deleteLater() + self.error_message = None + self.setFocus() + + @staticmethod + def create(field_type, parent=None): + if field_type is None: + return QStringInput() + if isinstance(field_type, ARRAY) and hasattr(field_type.item_type, 'enums'): + version = export_version() + if hasattr(field_type.item_type.enum_class, 'version'): + enum_values = [e for e in field_type.item_type.enums if field_type.item_type.enum_class[e].version in [None, version]] + else: + enum_values = field_type.item_type.enums + return QCheckableComboBoxInput(enum_values) + if hasattr(field_type, 'enums'): + should_include_default = isinstance(field_type, XPEnum) and field_type.include_default + version = export_version() + if hasattr(field_type.enum_class, 'version'): + enum_values = [e for e in field_type.enums if field_type.enum_class[e].version in [None, version]] + else: + enum_values = field_type.enums + return QComboBoxNoScroll(parent, items=enum_values, include_default=should_include_default) + if isinstance(field_type, RefURL): + return QFileInput() + if isinstance(field_type, LargeString): + return QTextInput() + if isinstance(field_type, Geometry): + from SAGisXPlanung.gui.widgets.QFeatureIdentify import QFeatureIdentify + return QFeatureIdentify() + if isinstance(field_type, XPlanungMeasureType): + return QMeasureTypeInput(field_type) + if isinstance(field_type, RegExString): + return QStringInput(field_type.expression, field_type.error_msg) + + if field_type.python_type == datetime.date: + return QDateEditNoScroll(parent, calendarPopup=True) + if field_type.python_type == bool: + return QBooleanInput() + if field_type.python_type == list and field_type.item_type.python_type == datetime.date: + return QDateListInput() + if field_type.python_type == int: + return QIntegerInput() + if field_type.python_type == float: + return QFloatInput() + + return QStringInput() + + +# workaround for mixin in abstract base class together with QObject +# https://stackoverflow.com/questions/28720217/multiple-inheritance-metaclass-conflict +class XPlanungInputMeta(type(QObject), type(QXPlanInputElement)): + pass + + +class LineEditMixin: + + def setInvalid(self, set_invalid): + super(LineEditMixin, self).setInvalid(set_invalid) + if set_invalid: + self.textChanged.connect(self.onControlEdited) + else: + self.textChanged.disconnect(self.onControlEdited) + + @pyqtSlot(str) + def onControlEdited(self, text): + self.setInvalid(False) + + +class QStringInput(LineEditMixin, QXPlanInputElement, QLineEdit, metaclass=XPlanungInputMeta): + + def __init__(self, regex=None, validation_error=None): + super(QStringInput, self).__init__() + self.regex = regex + self.validation_error = validation_error + + def value(self): + return self.text() or None + + def setDefault(self, default): + self.setText(default) + + def validate_widget(self, required): + if required and not bool(self.text()): + return False + if self.regex and bool(self.text()) and not bool(re.match(self.regex, self.text())): + self.error_message = self.validation_error + return False + return True + + +class QFloatInput(LineEditMixin, QXPlanInputElement, QLineEdit, metaclass=XPlanungInputMeta): + + def value(self): + return float(self.text()) if self.text() else None + + def setDefault(self, default): + self.setText('' if default is None else str(default)) + + def validate_widget(self, required): + try: + if not self.value() and required: + return False + return True + except ValueError: + self.error_message = 'Feld erwartet eine Zahl' + return False + + +class QIntegerInput(LineEditMixin, QXPlanInputElement, QLineEdit, metaclass=XPlanungInputMeta): + + def value(self): + return int(self.text()) if self.text() else None + + def setDefault(self, default): + self.setText('' if default is None else str(default)) + + def validate_widget(self, required): + try: + if not self.value() and required: + return False + return True + except ValueError: + self.error_message = 'Feld erwartet einen ganzzahligen Wert' + return False + + +class QBooleanInput(QXPlanInputElement, QWidget, metaclass=XPlanungInputMeta): + + def __init__(self): + super(QBooleanInput, self).__init__() + self.layout = QHBoxLayout() + self.option_yes = QRadioButton('Ja') + self.option_no = QRadioButton('Nein') + self.option_yes.toggled.connect(self.onRadioButtonToggle) + self.option_no.toggled.connect(self.onRadioButtonToggle) + self.clear_button = QPushButton('Auswahl entfernen') + self.clear_button.setCursor(Qt.PointingHandCursor) + self.clear_button.clicked.connect(self.clearSelection) + self.clear_button.hide() + self.clear_button.setStyleSheet(''' + QPushButton { + color: #64748b; + background: palette(window); + border: 0px; + } + QPushButton:hover { + color: #334155; + }''') + + self.layout.addWidget(self.option_yes) + self.layout.addWidget(self.option_no) + self.layout.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.layout.addWidget(self.clear_button) + self.group = QButtonGroup() + self.group.addButton(self.option_yes) + self.group.addButton(self.option_no) + + self.setLayout(self.layout) + + @pyqtSlot(bool) + def clearSelection(self, checked): + self.clear_button.hide() + # workaround for de-selecting radio buttons using QButtonGroup + # https://stackoverflow.com/questions/8689909/uncheck-radiobutton-pyqt4 + self.group.setExclusive(False) + self.option_yes.setChecked(False) + self.option_no.setChecked(False) + self.group.setExclusive(True) + + @pyqtSlot(bool) + def onRadioButtonToggle(self, checked): + if checked: + self.clear_button.show() + + def value(self): + if self.option_yes.isChecked(): + return True + if self.option_no.isChecked(): + return False + return None + + def setDefault(self, default): + self.option_yes.setChecked(default) + self.option_no.setChecked(not default) + + def validate_widget(self, required): + if required and self.value() is None: + return False + return True + + +class QFileInput(QXPlanInputElement, QgsFileWidget, metaclass=XPlanungInputMeta): + + def value(self): + return self.filePath() or None + + def file(self): + if not os.path.exists(self.filePath()): + return + with open(self.filePath(), "rb") as file: + return file.read() + + def setDefault(self, default): + self.setFilePath(default) + + def validate_widget(self, required): + if is_url(self.value()): + return True + if os.path.exists(self.filePath()): + # check for too large files + file_size = Path(self.value()).stat().st_size + if file_size/1000000 > 100: + self.error_message = 'Datei darf nicht größer als 100MB sein' + return False + return True + if not self.value() and not required: + return True + self.error_message = 'Feld erwartet eine URL oder einen gültigen Dateipfad' + return False + + def setInvalid(self, set_invalid): + super(QFileInput, self).setInvalid(set_invalid) + if set_invalid: + self.fileChanged.connect(self.onControlEdited) + else: + self.fileChanged.disconnect(self.onControlEdited) + + @pyqtSlot(str) + def onControlEdited(self, text): + self.setInvalid(False) + self.lineEdit().setFocus() + + +class QTextInput(QXPlanInputElement, QTextEdit, metaclass=XPlanungInputMeta): + + def value(self): + return self.toPlainText() or None + + def setDefault(self, default): + self.setText(default) + + def validate_widget(self, required): + return True + + +class QMeasureTypeInput(LineEditMixin, QXPlanInputElement, QLineEdit, metaclass=XPlanungInputMeta): + + def __init__(self, measure_type): + super(QMeasureTypeInput, self).__init__() + self.measure_type = measure_type + + def value(self): + if not self.text(): + return None + if isinstance(self.measure_type, (Scale, Angle)): + return int(self.text()) + if isinstance(self.measure_type, (Area, Volume, Length)): + return float(self.text()) + + def setDefault(self, default): + self.setText('' if default is None else str(default)) + + def error_message_text(self) -> str: + if isinstance(self.measure_type, Angle): + return 'Feld erwartet einen Winkel zwischen 0 und 360' + if isinstance(self.measure_type, (Area, Volume, Length)): + return 'Feld erwartet eine positive Zahl' + if isinstance(self.measure_type, Scale): + return 'Feld erwartet eine Prozentwert zwischen 0 und 100' + + def validate_widget(self, required): + try: + if not self.value() and not required: + return True + if isinstance(self.measure_type, Angle) and 0 <= int(self.value()) <= 360: + return True + elif isinstance(self.measure_type, (Area, Volume, Length)) and 0 <= float(self.value()) <= sys.float_info.max: + return True + elif isinstance(self.measure_type, Scale) and 0 <= int(self.value()) <= 100: + return True + raise ValueError + except (ValueError, TypeError): + self.error_message = self.error_message_text() + return False + + +class QDateListInput(LineEditMixin, QXPlanInputElement, QWidget, metaclass=XPlanungInputMeta): + + def __init__(self): + super(QDateListInput, self).__init__() + + self.layout = QVBoxLayout() + self.layout.setContentsMargins(0, 0, 0, 0) + self.inputs = [] + self.plus_icon_path = os.path.abspath(os.path.join(BASE_DIR, 'gui/resources/plus.svg')) + self.minus_icon_path = os.path.abspath(os.path.join(BASE_DIR, 'gui/resources/minus.svg')) + + self.first_input = QDateEditNoScroll(self, calendarPopup=True) + + self.add_button = QToolButton() + self.add_button.setIcon(self.loadSvg(self.plus_icon_path)) + self.add_button.installEventFilter(self) + self.add_button.setCursor(Qt.PointingHandCursor) + self.add_button.setToolTip('Weiteres Feld hinzufügen') + self.add_button.clicked.connect(self.addInput) + self.add_button.setStyleSheet(''' + QToolButton { + background: palette(window); + border: 0px; + } + ''') + + hbox = QHBoxLayout() + hbox.setSpacing(10) + hbox.addWidget(self.first_input) + hbox.addWidget(self.add_button) + self.layout.addItem(hbox) + self.inputs.append(hbox) + + self.setLayout(self.layout) + + def eventFilter(self, obj, event): + if event.type() == QEvent.HoverEnter: + obj.setIcon(self.loadSvg(obj.icon().pixmap(obj.icon().actualSize(QSize(32, 32))), color='#1F2937')) + elif event.type() == QEvent.HoverLeave: + obj.setIcon(self.loadSvg(obj.icon().pixmap(obj.icon().actualSize(QSize(32, 32))), color='#6B7280')) + return False + + def loadSvg(self, svg, color=None): + img = QPixmap(svg) + if color: + qp = QPainter(img) + qp.setCompositionMode(QPainter.CompositionMode_SourceIn) + qp.fillRect(img.rect(), QColor(color)) + qp.end() + return QIcon(img) + + def addInput(self, checked): + el = QDateEditNoScroll(self, calendarPopup=True) + hbox = QHBoxLayout() + hbox.setSpacing(10) + hbox.addWidget(el) + remove_button = QToolButton() + remove_button.setIcon(self.loadSvg(self.minus_icon_path)) + remove_button.installEventFilter(self) + remove_button.setCursor(Qt.PointingHandCursor) + remove_button.setToolTip('Eingabefeld entfernen') + remove_button.clicked.connect(self.removeInput) + remove_button.setStyleSheet(''' + QToolButton { + background: palette(window); + border: 0px; + } + ''') + hbox.addWidget(remove_button) + self.inputs.append(hbox) + self.layout.addLayout(hbox) + return el + + def removeInput(self, checked): + for layout in self.inputs: + for i in range(layout.count()): + if self.sender() == layout.itemAt(i).widget(): + self.inputs.remove(layout) + self.layout.removeItem(layout) + layout.deleteLater() + + def value(self): + input_fields = [layout.itemAt(0).widget() for layout in self.inputs] + dates = [] + for edit in input_fields: + if not edit.value(): + continue + dates.append(edit.value()) + + return dates + + def setDefault(self, default): + if not default: + return + dates = [datetime.datetime.strptime(date, "%d.%m.%Y") for date in str(default).split(', ')] + if not dates: + return + self.first_input.setDefault(dates[0]) + for date in dates[1:]: + el = self.addInput(None) + el.setDefault(date) + + def validate_widget(self, required): + return True + + +class QCheckableComboBoxInput(QXPlanInputElement, QgsCheckableComboBox, metaclass=XPlanungInputMeta): + + def __init__(self, items=None): + super(QCheckableComboBoxInput, self).__init__() + if items is not None: + self.addItems(items) + + def value(self): + return self.checkedItems() + + def setDefault(self, default): + items = str(default).split(", ") + self.setCheckedItems(items) + + def validate_widget(self, required): + if not required: + return True + return bool(self.checkedItems()) + + def setInvalid(self, set_invalid): + super(QCheckableComboBoxInput, self).setInvalid(set_invalid) + if set_invalid: + self.checkedItemsChanged.connect(self.onControlEdited) + else: + self.checkedItemsChanged.disconnect(self.onControlEdited) + + def onControlEdited(self, items): + self.setInvalid(False) + + +class QComboBoxNoScroll(QXPlanInputElement, QComboBox, metaclass=XPlanungInputMeta): + def __init__(self, scroll_widget=None, items=None, include_default=None, *args, **kwargs): + super(QComboBoxNoScroll, self).__init__(*args, **kwargs) + self.scrollWidget = scroll_widget + self.include_default = include_default + self.setFocusPolicy(Qt.StrongFocus) + + if items is not None: + self.addItems(items) + + if self.include_default: + # TODO: `setPlaceHolderText` is not working from Qt 5.15.2 upwards. (and was only introduced with 5.15.0) + # verify that it does work when QGIS moves to higher Qt version 5.15.9 or 6.0.1? + # https://bugreports.qt.io/browse/QTBUG-90595 + if QVersionNumber.fromString(qVersion()) > QVersionNumber.fromString('5.15.0'): + self.setPlaceholderText('-- Keine Auswahl --') + self.setCurrentIndex(-1) + + def wheelEvent(self, *args, **kwargs): + if self.hasFocus() or self.scrollWidget is None: + return super(QComboBoxNoScroll, self).wheelEvent(*args, **kwargs) + + return self.scrollWidget.wheelEvent(*args, **kwargs) + + def value(self): + if self.currentIndex() == -1: + return None + return self.currentText() + + def setDefault(self, default): + index = self.findText(str(default)) + if index >= 0: + self.setCurrentIndex(index) + + def validate_widget(self, required): + return True + + +class QDateEditNoScroll(QXPlanInputElement, QgsDateEdit, metaclass=XPlanungInputMeta): + + def __init__(self, scroll_widget=None, *args, **kwargs): + super(QDateEditNoScroll, self).__init__(*args, **kwargs) + self.scrollWidget = scroll_widget + self.setFocusPolicy(Qt.StrongFocus) + + self.setAllowNull(True) + self.setNullRepresentation('') + self.clear() + + def wheelEvent(self, *args, **kwargs): + if self.hasFocus() or self.scrollWidget is None: + return super(QDateEditNoScroll, self).wheelEvent(*args, **kwargs) + + return self.scrollWidget.wheelEvent(*args, **kwargs) + + def mousePressEvent(self, event): + self.setDate(QDate.currentDate()) + super(QDateEditNoScroll, self).mousePressEvent(event) + + def value(self): + if self.isNull(): + return + date = self.date().toPyDate() + if date != PYQT_DEFAULT_DATE: + return date + + def setDefault(self, default): + self.setDate(default) + + def validate_widget(self, required): + return True diff --git a/src/SAGisXPlanung/gui/widgets/QXPlanScrollPage.py b/src/SAGisXPlanung/gui/widgets/QXPlanScrollPage.py new file mode 100644 index 0000000..7d04fdf --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QXPlanScrollPage.py @@ -0,0 +1,359 @@ +import datetime +import logging +import os +from collections import namedtuple +from inspect import getmro +from pathlib import Path +from typing import List + +import yaml +from qgis.PyQt import QtCore, QtWidgets, QtGui +from qgis.PyQt.QtCore import Qt, QSettings +from qgis.PyQt.QtWidgets import QFrame, QSpacerItem, QSizePolicy, QGridLayout, QGroupBox + +from sqlalchemy import inspect, null +from sqlalchemy.orm import class_mapper +from sqlalchemy.orm.exc import UnmappedClassError + +from SAGisXPlanung import Session, BASE_DIR, Base +from SAGisXPlanung.XPlan.feature_types import XP_Objekt +from SAGisXPlanung.XPlan.types import InvalidFormException +from SAGisXPlanung.config import xplan_tooltip, export_version +from SAGisXPlanung.gui.widgets.QRelationDropdowns import QAddRelationDropdown +from SAGisXPlanung.gui.widgets.QXPlanInputElement import QXPlanInputElement, QFileInput + +PYQT_DEFAULT_DATE = datetime.date(1752, 9, 14) +logger = logging.getLogger(__name__) + + +class QXPlanScrollPage(QtWidgets.QScrollArea): + + addRelationRequested = QtCore.pyqtSignal(object, bool, str) + parentLinkClicked = QtCore.pyqtSignal() + requestPageToTop = QtCore.pyqtSignal() + + def __init__(self, cls_type, parent_class, parent_attribute=None, existing_xid=None, *args, **kwargs): + super(QXPlanScrollPage, self).__init__(*args, **kwargs) + self.cls_type = cls_type + self.parent_class = parent_class + self.parent_attribute = parent_attribute + self.existing_xid = existing_xid + self.child_pages = [] + self.fields = {} + self.required_inputs = [] + self.hidden_inputs = [] + + s = QSettings() + self.ATTRIBUTE_CONFIG = yaml.safe_load(s.value(f"plugins/xplanung/attribute_config", '')) or {} + + if hasattr(cls_type, 'hidden_inputs'): + self.hidden_inputs = cls_type.hidden_inputs() + + self.setFrameShape(QFrame.NoFrame) + self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.horizontalScrollBar().setEnabled(False) + self.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + + self.content_widget = QtWidgets.QWidget() + self.setWidget(self.content_widget) + self.vBox = QtWidgets.QVBoxLayout() + self.vBox.setSpacing(20) + self.content_widget.setLayout(self.vBox) + + if self.parent_class is not None: + self.createHeader() + self.createLayout() + + spacer = QtWidgets.QWidget() + spacer.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + self.vBox.addWidget(spacer) + + def sizeHint(self): + size = super(QXPlanScrollPage, self).sizeHint() + size.setWidth(size.width() + 5 * self.verticalScrollBar().sizeHint().width()) + return size + + def addChildPage(self, page): + self.child_pages.append(page) + + def removeChildPage(self, page): + self.child_pages.remove(page) + + def createHeader(self): + widget = QtWidgets.QWidget(self) + widget.setStyleSheet(""" + QLabel#lParentAttribute { + color: #64748b; + } + QPushButton#lParentClass{ + color: #1e293b; + background: palette(window); + border: 0px; + } + QPushButton#lParentClass:hover { + color: #0ea5e9; + } + """) + h_layout = QtWidgets.QHBoxLayout(widget) + h_layout.setContentsMargins(0, 10, 0, 10) + l1 = QtWidgets.QLabel('Objekt gehört zu:') + l2 = QtWidgets.QPushButton(self.parent_class.__name__, objectName='lParentClass') + l1.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + l2.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + h_layout.addWidget(l1) + h_layout.addWidget(l2) + if self.parent_attribute: + l3 = QtWidgets.QLabel(f'(Attribut: {self.parent_attribute})', objectName='lParentAttribute') + l2.setCursor(Qt.PointingHandCursor) + l2.clicked.connect(lambda checked: self.parentLinkClicked.emit()) + h_layout.addWidget(l3) + else: + l2.setDisabled(True) + h_layout.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.vBox.addWidget(widget) + + def createLayout(self): + column_resizer = ColumnResizer() + + for cls in self.cls_type.mro(): + # catches base classes which are no sqlalchemy models + if not is_mapped(cls): + continue + + group_box = QGroupBox(cls.__name__) + grid = QtWidgets.QGridLayout(group_box) + + rel_offset = 0 + for rel in inspect(cls).relationships.items(): + class_type = rel[1].mapper.class_ + + # don't show if relationship property does not fit export version + if not cls.relation_fits_version(rel[0], export_version()): + continue + + # don't show, if relationship is treated as column + if hasattr(cls, f'{rel[0]}_id') and cls.attr_is_treated_as_column(f'{rel[0]}_id', consider_suffix=False): + continue + + # disallow specifically declared relations + if hasattr(cls, '__avoidRelation__') and rel[0] in cls.__avoidRelation__: + continue + + # disallow inherited relationships + if rel[1].parent.class_ != cls: + continue + + # avoid referencing of circular dependencies + if class_type == self.parent_class or \ + (self.parent_class is not None and issubclass(self.parent_class, class_type)): + continue + + # avoid references to XP_Objekt's. They are set via the 'Planinhalt konfigurieren' Dialog. + if class_type == XP_Objekt: + continue + + + label_name, tooltip = cls.relation_prop_display(rel) + label = QtWidgets.QLabel(label_name) + if tooltip: + label.setToolTip(tooltip) + + label.setObjectName(rel[0]) + required_rel = bool(hasattr(cls, '__requiredRelationships__') and label.objectName() in cls.__requiredRelationships__) + if required_rel: + label.setStyleSheet("font-weight: bold") + self.required_inputs.append(label.objectName()) + grid.addWidget(label, rel_offset, 0) + + # complex many-to-many or many-to-one relation + if next(iter(rel[1].remote_side)).primary_key or rel[1].secondary is not None: + widget = QAddRelationDropdown(self, rel) + + # one-to-many relation, one-to-one relation (defined by `uselist=False`) + else: + widget = QtWidgets.QPushButton("Hinzufügen") + try: + if rel[0] in self.cls_type.__requiredRelationships__: + self.addRelationRequested.emit(class_type) + except AttributeError: + pass + widget.clicked.connect(lambda state, c=class_type, u=rel[1].uselist, a=rel[0]: + self.onRelationButtonClicked(c, u, a)) + + self.fields[rel[0]] = widget + grid.addWidget(widget, rel_offset, 1) + rel_offset += 1 + + col_skip = 0 + for i, key in enumerate(cls.element_order(include_base=False, only_columns=True, export=False, + version=export_version())): + if key in self.hidden_inputs: + col_skip += 1 + continue + # don't show attributes that are disabled in settings + if key in self.ATTRIBUTE_CONFIG.get(cls.__name__, []): + col_skip += 1 + continue + + label, control = self.createInput(key, cls) + control.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + grid.addWidget(label, i + rel_offset - col_skip, 0) + grid.addWidget(control, i + rel_offset - col_skip, 1) + + self.fields[key] = control + + # don't add group boxes that are empty + if grid.count() == 0: + continue + + column_resizer.form_widgets.append(grid) + self.vBox.addWidget(group_box) + + column_resizer.applyResize() + + def onRelationButtonClicked(self, class_type, uselist, parent_attribute): + if not uselist: + button = self.fields[parent_attribute] + button.setDisabled(True) + self.addRelationRequested.emit(class_type, uselist, parent_attribute) + + def createInput(self, label_name: str, cls: type): + # if column is a relationship-column, configure relation dropdown instead of normal input element + if (rel := next((r for r in cls.relationships() if r[0] == label_name), None)) is not None: + stub = namedtuple('stub', ['cls_type']) + control = QAddRelationDropdown(stub(cls_type=self.cls_type), rel) + + label = QtWidgets.QLabel(label_name) + tooltip = xplan_tooltip(self.cls_type, label_name) + label.setToolTip(tooltip) + + return label, control + + base_classes = [c for c in list(getmro(cls)) if issubclass(c, Base)] + cls = next(c for c in base_classes if + hasattr(c, label_name) and c.attr_fits_version(label_name, export_version())) + column = getattr(cls, label_name).property.columns[0] + field_type = column.type + nullable = column.nullable + + control = QXPlanInputElement.create(field_type, self) + + if column.doc: + label = QtWidgets.QLabel(column.doc) + label.setToolTip(f'XPlanung-Attribut: {label_name}') + else: + label = QtWidgets.QLabel(label_name) + tooltip = xplan_tooltip(self.cls_type, label_name) + label.setToolTip(tooltip) + + label.setObjectName(label_name) + + if not nullable: + font = QtGui.QFont() + font.setBold(True) + label.setFont(font) + self.required_inputs.append(label_name) + + return label, control + + def getObjectFromInputs(self, validate_forms=True): + if validate_forms and not self.validateForms(): + raise InvalidFormException() + + obj = self.cls_type() + if self.existing_xid: + obj.id = self.existing_xid + + for column, input_field in self.fields.items(): + if isinstance(input_field, QAddRelationDropdown): + class_type = getattr(obj.__class__, column).property.mapper.class_ + if input_field.relation[1].secondary is None: + selected_object = input_field.value() + if not selected_object: # no relation selected + setattr(obj, f'{column}_id', null()) + continue + with Session.begin() as session: + obj_from_db = session.merge(selected_object) + setattr(obj, f'{column}_id', obj_from_db.id) + setattr(obj, column, obj_from_db) + else: + selected_objects = input_field.value() + with Session.begin() as session: + obj_list = [] + for selected_item in input_field.value(): + obj_list.append(session.merge(selected_item)) + setattr(obj, column, obj_list) + continue + + if not isinstance(input_field, QXPlanInputElement): + continue + + setattr(obj, column, input_field.value()) + + if isinstance(input_field, QFileInput): + setattr(obj, 'file', input_field.file()) + + return obj + + def validateForms(self): + is_valid = True + invalid_attributes = [] + for attribute, control in self.fields.items(): + if not isinstance(control, QXPlanInputElement): + continue + + required_input = bool(attribute in self.required_inputs) + if not control.validate_widget(required_input): + is_valid = False + control.setInvalid(True) + invalid_attributes.append(attribute) + + if not is_valid: + label = self.findChild(QtWidgets.QLabel, invalid_attributes[0]) + self.ensureWidgetVisible(label) + self.requestPageToTop.emit() + + return is_valid + + +def is_mapped(obj): + try: + class_mapper(obj) + except UnmappedClassError: + return False + return True + +# **************************************************************** + + +class ColumnResizer: + """ + Helper-Klasse zum Anpassen des Layouts der Eingabeformulare. + Gleicht die Breite der Spalten mehrerer voneinander unabhängiger GridLayouts an. + + Inspiriert durch https://github.com/agateau/columnresizer + """ + + def __init__(self): + self.form_widgets: List[QGridLayout] = [] + + def applyResize(self): + largest_size = 0 + + # find largest label size + for widget in self.form_widgets: + # prevent empty layouts + if widget.rowCount() < 1: + continue + for i in range(widget.rowCount()): + label = widget.itemAtPosition(i, 0).widget() + size = label.minimumSizeHint().width() + largest_size = max(largest_size, size) + + # apply resizing: set all labels to minimum width + for widget in self.form_widgets: + if widget.rowCount() < 1: + continue + for i in range(widget.rowCount()): + label = widget.itemAtPosition(i, 0).widget() + label.setMinimumWidth(largest_size) diff --git a/src/SAGisXPlanung/gui/widgets/QXPlanTabWidget.py b/src/SAGisXPlanung/gui/widgets/QXPlanTabWidget.py new file mode 100644 index 0000000..528d812 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/QXPlanTabWidget.py @@ -0,0 +1,142 @@ +import itertools + +from qgis.PyQt.QtCore import pyqtSlot +from qgis.PyQt import QtWidgets + +from sqlalchemy import inspect + +from SAGisXPlanung.XPlan.types import ConformityException, InvalidFormException +from SAGisXPlanung.gui.widgets.QXPlanScrollPage import QXPlanScrollPage + + +class QXPlanTabWidget(QtWidgets.QTabWidget): + def __init__(self, main_type, parent_type=None, *args, **kwargs): + super(QXPlanTabWidget, self).__init__(*args, **kwargs) + self.main_type = main_type + self.parent_type = parent_type + self.close_warning = None + + self.setTabsClosable(True) + self.tabCloseRequested.connect(lambda index: self.closeTab(index)) + self.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + + self.createTab(self.main_type, parent=parent_type) + + def closeTab(self, index): + tab_to_close: QXPlanScrollPage = self.widget(index) + + should_close = self.shouldCloseTab(tab_to_close) + if not should_close: + return + + # for all pages, check if current page is a child of page. if yes, enable button and remove from children + for i in range(0, self.count()): + if i == index: + continue + page: QXPlanScrollPage = self.widget(i) + if tab_to_close in page.child_pages: + button = page.fields[tab_to_close.parent_attribute] + button.setEnabled(True) + page.removeChildPage(tab_to_close) + + for child_page in tab_to_close.child_pages: + self.removeTab(self.indexOf(child_page)) + self.removeTab(index) + + def shouldCloseTab(self, tab: QXPlanScrollPage) -> bool: + # always close if no dependencies present + if len(tab.child_pages) == 0: + return True + + msg = f'Es gibt abhängige Objekte vom Typ
      ' + for child_page in tab.child_pages: + msg += f"
    • {child_page.cls_type.__name__}
    • " + msg += '
    Trotzdem löschen?' + + self.close_warning = QtWidgets.QMessageBox.information(self, f'{tab.cls_type.__name__} entfernen', msg, + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) + + return self.close_warning == QtWidgets.QMessageBox.Yes + + def createTab(self, cls, parent=None, parent_attribute=None, existing_xid=None, closable=False): + scroll = QXPlanScrollPage(cls, parent, parent_attribute, existing_xid, widgetResizable=True) + scroll.addRelationRequested.connect( + lambda cls_type, uselist, attr: self.onAddRelationRequested(cls, cls_type, uselist, attr)) + scroll.parentLinkClicked.connect(self.onParentLinkClicked) + scroll.requestPageToTop.connect(lambda: self.setCurrentWidget(self.sender())) + + if self.currentIndex() != -1: + current_page: QXPlanScrollPage = self.widget(self.currentIndex()) + current_page.addChildPage(scroll) + + self.insertTab(self.currentIndex() + 1, scroll, cls.__name__) + self.setCurrentIndex(self.currentIndex() + 1) + + if not closable: + self.tabBar().tabButton(self.currentIndex(), QtWidgets.QTabBar.RightSide).resize(0, 0) + + def onAddRelationRequested(self, cls, cls_type, uselist, parent_attribute): + tab_exists = any([self.widget(index).parent_attribute == parent_attribute for index in range(self.count())]) + if not uselist and tab_exists: + return + self.createTab(cls_type, parent=cls, parent_attribute=parent_attribute, closable=True) + + @pyqtSlot() + def onParentLinkClicked(self): + for i in range(0, self.count()): + if i == self.currentIndex(): + continue + page: QXPlanScrollPage = self.widget(i) + if self.sender() in page.child_pages: + self.setCurrentWidget(page) + + def populateContent(self): + try: + + obj = self.widget(0).getObjectFromInputs() + if hasattr(obj, 'validate'): + obj.validate() + + self.populateRelations(obj) + + return obj + except ConformityException as e: + msg = QtWidgets.QMessageBox() + msg.setIcon(QtWidgets.QMessageBox.Warning) + + msg.setText(e.message) + msg.setWindowTitle(f"Konformitätsbedingung {e.code} für das Objekt {e.object_type} verletzt") + msg.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) + ret = msg.exec_() + except InvalidFormException: + pass + + def populateRelations(self, obj, is_subrel=False, parent_class=None, current_index=None): + for rel in inspect(obj.__class__).relationships.items(): + if parent_class == rel[1].mapper.class_ or hasattr(obj, f'{rel[0]}_id'): + continue + + if not is_subrel: + tab_widgets = [self.widget(index) for index in range(self.count()) + if rel[1].mapper.class_.__name__ == self.tabText(index)] + else: + tab_widgets = [self.widget(index) for index in + itertools.takewhile(lambda x: rel[1].mapper.class_.__name__ == self.tabText(x), + range(current_index + 1, self.count()))] + for tab in tab_widgets: + # check for correct tab, if multiple relations of same type are present + if rel[0] != tab.parent_attribute: + continue + + relation = tab.getObjectFromInputs() + if hasattr(relation, 'validate'): + relation.validate() + + # append or set data, depending on whether relation is one-to-one or one-to-many + if rel[1].uselist: + getattr(obj, rel[0]).append(relation) + else: + setattr(obj, rel[0], relation) + + i = self.indexOf(tab) + self.populateRelations(relation, is_subrel=True, parent_class=tab.parent_class, current_index=i) diff --git a/src/SAGisXPlanung/gui/widgets/__init__.py b/src/SAGisXPlanung/gui/widgets/__init__.py new file mode 100644 index 0000000..5a7ca0e --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/__init__.py @@ -0,0 +1,7 @@ +from .template_edit import QBuildingTemplateEdit +from .po_styling_options import QCommonStylingOptions +from .parish_edit import QParishEdit, QParishLabel +from .svg_symbol_edit import SVGSymbolDisplayWidget +from .selected_geoms_view import QSelectedGeometriesView +from .commons import ElideLabel +from .attribute_config import QAttributeConfigView diff --git a/src/SAGisXPlanung/gui/widgets/attribute_config.py b/src/SAGisXPlanung/gui/widgets/attribute_config.py new file mode 100644 index 0000000..1b4cb29 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/attribute_config.py @@ -0,0 +1,352 @@ +import functools +import html +import logging +from enum import Enum +import yaml + +from qgis.PyQt.QtGui import QFontMetrics, QIcon +from qgis.PyQt.QtCore import (QAbstractItemModel, Qt, QModelIndex, QSettings, QSortFilterProxyModel, + pyqtSlot, QObject, QEvent, QPoint, QSize) +from qgis.PyQt.QtWidgets import (QTreeView, QHeaderView, QAbstractItemView, QToolTip, QMenu, QAction) +from sqlalchemy import inspect + +from SAGisXPlanung import BASE_DIR +from SAGisXPlanung.config import export_version +from SAGisXPlanung.XPlan.mixins import ElementOrderMixin +from SAGisXPlanung.config import xplan_tooltip +from SAGisXPlanung.gui.style import TagStyledDelegate, HighlightRowProxyStyle +from SAGisXPlanung.utils import CLASSES + +logger = logging.getLogger(__name__) + + +class SortOptions(Enum): + SortHierarchy = 0 + SortAlphabet = 1 + SortCategory = 2 + + +class QAttributeConfigView(QTreeView): + + def __init__(self, parent=None): + super(QAttributeConfigView, self).__init__(parent) + + self._model = AttributeConfigModel() + + self.proxy = SortProxyModel(self) + self.proxy.setSourceModel(self._model) + self.setModel(self.proxy) + + self.setItemDelegate(TagStyledDelegate()) + self.proxy_style = HighlightRowProxyStyle('Fusion') + self.proxy_style.setParent(self) + self.setStyle(self.proxy_style) + self.setMouseTracking(True) + + self.viewport().installEventFilter(QToolTipper(self)) + self.setContextMenuPolicy(Qt.CustomContextMenu) + self.customContextMenuRequested.connect(self.onContextMenuRequested) + + # header setup + self.header().setStretchLastSection(False) + self.header().setSectionResizeMode(0, QHeaderView.Stretch) + self.header().setSectionResizeMode(1, QHeaderView.Stretch) + + # model setup + self.setupModelData() + + def setupModelData(self): + self.clear() + + s = QSettings() + config = yaml.safe_load(s.value(f"plugins/xplanung/attribute_config", '')) or {} + + for xplan_class_name, xplan_class in CLASSES.items(): + node = AttributeTreeNode(xplan_class_name, '', False) + + if not issubclass(xplan_class, ElementOrderMixin): + continue + + cols = inspect(xplan_class).columns + for attribute in xplan_class.element_order(include_base=False, only_columns=True, + version=export_version()): + try: + index = cols.keys().index(attribute) + nullable = cols[index].nullable + except ValueError: + nullable = True + + desc = xplan_tooltip(xplan_class, attribute, plain=True) + unchecked = attribute in config.get(xplan_class_name, []) + attribute_node = AttributeTreeNode(attribute, desc, not unchecked, required=not nullable) + node.addChild(attribute_node) + self._model.addChild(node) + + self.expandAll() + + def sizeHint(self): + size = super(QAttributeConfigView, self).sizeHint() + return QSize(size.width(), 80) + + def clear(self): + self._model.clear() + + @pyqtSlot(str) + def onFilterTextChanged(self, text: str): + self.proxy.setFilterRegularExpression(text) + + def config_dict(self): + config = {} + + for top_node in self._model._root._children: + config[top_node.name] = [node.name for node in top_node._children if not node.is_checked] + + return config + + @pyqtSlot(QPoint) + def onContextMenuRequested(self, pos: QPoint): + """ context menu to select/deselect all attributes within a category """ + index = self.proxy.mapToSource(self.indexAt(pos)) + + # return if index is not a top level item (category items are always top-level) + if index.parent().isValid(): + return + + menu = QMenu() + menu.setToolTipsVisible(True) + + select_all_action = QAction(QIcon(':/images/themes/default/mActionSelectAllTree.svg'), 'Alles wählen') + select_all_action.setToolTip('Alle Attribute dieser Objektklasse wählen.') + select_all_action.triggered.connect(functools.partial(self.onSelectCategoryTriggered, index, True)) + menu.addAction(select_all_action) + + deselect_all_action = QAction(QIcon(':/images/themes/default/mActionDeselectAllTree.svg'), 'Alles abwählen') + deselect_all_action.setToolTip('Alle Attribute dieser Objektklasse abwählen. Verpflichtende Attribute werden nicht abgewählt.') + deselect_all_action.triggered.connect(functools.partial(self.onSelectCategoryTriggered, index, False)) + menu.addAction(deselect_all_action) + + menu.exec_(self.viewport().mapToGlobal(pos)) + + @pyqtSlot(QModelIndex, bool, bool) + def onSelectCategoryTriggered(self, index: QModelIndex, select: bool, checked: bool): + node = index.internalPointer() + + for i in range(node.childCount()): + match = self._model.index(i, 0, index) + self._model.setData(match.siblingAtColumn(2), select, Qt.CheckStateRole) + + +class AttributeConfigModel(QAbstractItemModel): + def __init__(self, nodes=None): + super(AttributeConfigModel, self).__init__() + if nodes is None: + nodes = [] + self._horizontal_header = ['XPlanung-Attribut', 'Beschreibung', 'Anzeigen'] + + self._root = AttributeTreeNode() + for node in nodes: + self._root.addChild(node) + + def clear(self): + self.beginResetModel() + for i in range(self._root.childCount()): + item = self._root.child(i) + del item + self._root._children.clear() + self.endResetModel() + + def addChild(self, node, _parent=QModelIndex()): + row = self.rowCount(_parent) + if not _parent or not _parent.isValid(): + parent = self._root + else: + parent = _parent.internalPointer() + self.beginInsertRows(_parent, row, row) + parent.addChild(node) + self.endInsertRows() + + def removeRows(self, row: int, count: int, parent: QModelIndex = None) -> bool: + self.beginRemoveRows(parent, row, row + count - 1) + parent_item = self.itemAtIndex(parent) + for i in range(count): + parent_item.removeChild(row + i) + self.endRemoveRows() + return True + + def data(self, index, role=Qt.DisplayRole): + if not index.isValid(): + return None + node = index.internalPointer() + if role == Qt.DisplayRole: + return node.data(index.column()) + if role == Qt.ToolTipRole: + return '{}'.format(html.escape(node.data(index.column()))) + if role == Qt.CheckStateRole and index.column() == 2 and index.parent().isValid(): + return node.checked() + return None + + def flags(self, index: QModelIndex): + current_flags = super(AttributeConfigModel, self).flags(index) + flags = current_flags & ~Qt.ItemIsSelectable + + if not index.parent().isValid(): + return flags & ~Qt.ItemIsEnabled + + node = index.internalPointer() + if node.required: + return flags & ~Qt.ItemIsEnabled + if index.column() == 2: + return flags | Qt.ItemIsUserCheckable + + return flags + + def setData(self, index, value, role=Qt.EditRole): + if index.column() == 2: + if role == Qt.EditRole: + return False + if role == Qt.CheckStateRole: + item = self.itemAtIndex(index) + if item.required: + return True + item.is_checked = value + self.dataChanged.emit(index, index) + return True + + return super(AttributeConfigModel, self).setData(index, value, role) + + def headerData(self, col, orientation, role=Qt.DisplayRole): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self._horizontal_header[col] + + def itemAtIndex(self, index: QModelIndex): + if not index.isValid(): + return self._root + + return index.internalPointer() + + def index(self, row, column, _parent=QModelIndex()): + parentItem = self.itemAtIndex(_parent) + child = parentItem.child(row) + if child: + return self.createIndex(row, column, child) + else: + return QModelIndex() + + def parent(self, index): + if not index.isValid(): + return QModelIndex() + + child = self.itemAtIndex(index) + parentItem = child.parent() + if parentItem == self._root: + return QModelIndex() + + return self.createIndex(parentItem.row(), 0, parentItem) + + def rowCount(self, index): + if index.isValid(): + return index.internalPointer().childCount() + return self._root.childCount() + + def columnCount(self, index): + return 3 + + +class SortProxyModel(QSortFilterProxyModel): + def __init__(self, parent=None): + super().__init__(parent) + + self.setRecursiveFilteringEnabled(True) + self.setFilterCaseSensitivity(Qt.CaseInsensitive) + + def filterAcceptsRow(self, source_row: int, parent: QModelIndex): + index = self.sourceModel().index(source_row, 0, parent) + + # accept if any of the parents is accepted + while parent.isValid(): + if self.filterAcceptsRow(parent.row(), parent.parent()): + return True + parent = parent.parent() + + return self.filterRegularExpression().pattern().lower() in str(self.sourceModel().data(index)).lower() + + +# ****************** NODES **************************** + +class AttributeTreeNode: + def __init__(self, name: str = None, description: str = None, is_checked: bool = None, required=False): + self._children = [] + self._parent = None + self._row = 0 + + self.name = name + self.description = description + self.is_checked = is_checked + self.column_count = 3 + + self.required = required + + def data(self, column): + if column == 0: + return self.name + if column == 1: + return self.description + + def checked(self): + if self.required: + return Qt.Checked + return Qt.Checked if self.is_checked else Qt.Unchecked + + def childCount(self): + return len(self._children) + + def child(self, row): + if 0 <= row < self.childCount(): + return self._children[row] + + def parent(self): + return self._parent + + def row(self): + return self._row + + def addChild(self, child): + child._parent = self + child._row = len(self._children) + self._children.append(child) + + def removeChild(self, row): + if row < 0 or row > len(self._children): + return + child = self._children.pop(row) + child._parent = None + + # update child rows to reflect changes + for i in range(len(self._children)): + self.child(i)._row = i + + +# ****************** EVENT FILTER **************************** + +class QToolTipper(QObject): + + def eventFilter(self, obj: QObject, event: QEvent): + if event.type() == QEvent.ToolTip: + view: QAbstractItemView = obj.parent() + if not view: + return False + index = view.indexAt(event.pos()) + if not index.isValid(): + return False + + item_text = view.model().data(index) + fm = QFontMetrics(view.font()) + item_text_width = fm.width(item_text) + rect = view.visualRect(index) + if len(item_text) != 0 and item_text_width > rect.width(): + tooltip = view.model().data(index, Qt.ToolTipRole) + QToolTip.showText(event.globalPos(), tooltip, view, rect) + else: + QToolTip.hideText() + return True + return False diff --git a/src/SAGisXPlanung/gui/widgets/commons.py b/src/SAGisXPlanung/gui/widgets/commons.py new file mode 100644 index 0000000..4ed46b4 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/commons.py @@ -0,0 +1,41 @@ +from qgis.PyQt.QtWidgets import QLabel, QStyleOptionFrame, QStyle +from qgis.PyQt import QtCore, QtGui + + +class ElideLabel(QLabel): + _elideMode = QtCore.Qt.ElideMiddle + + def elideMode(self): + return self._elideMode + + def setElideMode(self, mode): + if self._elideMode != mode and mode != QtCore.Qt.ElideNone: + self._elideMode = mode + self.updateGeometry() + + def minimumSizeHint(self): + return self.sizeHint() + + def sizeHint(self): + hint = self.fontMetrics().boundingRect(self.text()).size() + l, t, r, b = self.getContentsMargins() + margin = self.margin() * 2 + return QtCore.QSize( + min(100, hint.width()) + l + r + margin, + min(self.fontMetrics().height(), hint.height()) + t + b + margin + ) + + def paintEvent(self, event): + qp = QtGui.QPainter(self) + opt = QStyleOptionFrame() + self.initStyleOption(opt) + self.style().drawControl(QStyle.CE_ShapedFrame, opt, qp, self) + l, t, r, b = self.getContentsMargins() + margin = self.margin() + try: + # since Qt >= 5.11 + m = self.fontMetrics().horizontalAdvance('x') / 2 - margin + except: + m = self.fontMetrics().width('x') / 2 - margin + r = self.contentsRect().adjusted(margin + m, margin, -(margin + m), -margin) + qp.drawText(r, self.alignment(), self.fontMetrics().elidedText(self.text(), self.elideMode(), r.width())) diff --git a/src/SAGisXPlanung/gui/widgets/parish_edit.py b/src/SAGisXPlanung/gui/widgets/parish_edit.py new file mode 100644 index 0000000..85329e9 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/parish_edit.py @@ -0,0 +1,149 @@ +import os +from typing import List + +import qasync +from qgis.PyQt.QtWidgets import QWidget, QGroupBox, QVBoxLayout, QHBoxLayout, QComboBox, QLabel, QToolButton, QSizePolicy, QFrame +from qgis.PyQt.QtCore import Qt, QPoint, QSize, pyqtSlot, QRect, QEvent, pyqtSignal +from qgis.PyQt.QtGui import QIcon, QPixmap, QPainter, QColor + +from SAGisXPlanung import BASE_DIR, Session +from SAGisXPlanung.XPlan.data_types import XP_Gemeinde +from SAGisXPlanung.gui.widgets.QXPlanInputElement import QCheckableComboBoxInput + + +class QParishLabel(QWidget): + IconSize = QSize(16, 16) + HorizontalSpacing = 2 + + parishEditRequested = pyqtSignal() + + def __init__(self, parent): + super(QWidget, self).__init__(parent) + + self.setMouseTracking(True) + self.active = True + + layout = QHBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + self.setLayout(layout) + + self.icon_button = QToolButton() + self.icon_button.setIcon(QIcon(os.path.join(BASE_DIR, 'gui/resources/location_edit.svg'))) + self.icon_button.setStyleSheet(''' + QToolButton { + background: palette(window); + border: 0px; + border-radius: 3px; + } + ''') + self.icon_button.clicked.connect(self.onLocationEdit) + self.icon_button.setCursor(Qt.PointingHandCursor) + self.icon_button.setVisible(False) + self.installEventFilter(self) + + self.label = QLabel('') + layout.addWidget(self.label) + layout.addWidget(self.icon_button) + + def setActive(self, active: bool): + """ Whether the editing capabilities are activated """ + self.active = active + + def eventFilter(self, obj, event): + # if editing is not active do nothing + if not self.active: + return False + + if event.type() == QEvent.Enter: + self.icon_button.show() + elif event.type() == QEvent.Leave: + self.icon_button.hide() + return False + + def setText(self, text): + self.label.setText(text) + + @pyqtSlot(bool) + def onLocationEdit(self, clicked): + self.parishEditRequested.emit() + + +class QParishEdit(QWidget): + + parishChanged = pyqtSignal(dict) + + def __init__(self, parent=None): + + super(QParishEdit, self).__init__(parent) + + self.cb = QCheckableComboBoxInput() + self.cb.checkedItemsChanged.connect(self.onControlEdited) + + self.group = QFrame() + self.group.setFrameStyle(QFrame.StyledPanel) + self.group.setLayout(QVBoxLayout()) + self.group.layout().addWidget(QLabel('Gemeinde auswählen:')) + self.group.layout().addWidget(self.cb) + + self.close_button = QToolButton() + self.close_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum) + self.close_button.setIcon(self.loadSvg(os.path.join(BASE_DIR, 'gui/resources/expand_less.svg'))) + self.close_button.installEventFilter(self) + self.close_button.setCursor(Qt.PointingHandCursor) + self.close_button.setToolTip('Sektion schließen') + self.close_button.setIconSize(QSize(24, 24)) + self.close_button.setStyleSheet(''' + QToolButton { + background: palette(window); + border: 0px; + border-radius: 3px; + } + ''') + self.close_button.clicked.connect(self.onExpandLessClicked) + + self.setLayout(QHBoxLayout()) + self.layout().addWidget(self.group) + self.layout().addWidget(self.close_button, Qt.AlignCenter) + self.layout().setContentsMargins(0, 0, 0, 0) + + def setup(self, parish_objects: List[XP_Gemeinde]): + self.cb.clear() + if self.cb.invalid: + self.cb.setInvalid(False) + with Session.begin() as session: + xp_gemeinde = session.query(XP_Gemeinde).all() + for g in xp_gemeinde: + match = bool(any(g == p for p in parish_objects)) + self.cb.addItemWithCheckState(str(g), Qt.Checked if match else Qt.Unchecked, str(g.id)) + + @pyqtSlot(bool) + def onExpandLessClicked(self, clicked): + self.hide() + + @qasync.asyncSlot('QStringList') + async def onControlEdited(self, checked_items): + user_data = self.cb.checkedItemsData() + parish = {} + for i, item in enumerate(checked_items): + parish[item] = user_data[i] + + if not self.cb.validate_widget(required=True): + self.cb.setInvalid(True) + return + self.parishChanged.emit(parish) + + def eventFilter(self, obj, event): + if event.type() == QEvent.HoverEnter: + obj.setIcon(self.loadSvg(obj.icon().pixmap(obj.icon().actualSize(QSize(32, 32))), color='#1F2937')) + elif event.type() == QEvent.HoverLeave: + obj.setIcon(self.loadSvg(obj.icon().pixmap(obj.icon().actualSize(QSize(32, 32))), color='#6B7280')) + return False + + def loadSvg(self, svg, color=None): + img = QPixmap(svg) + if color: + qp = QPainter(img) + qp.setCompositionMode(QPainter.CompositionMode_SourceIn) + qp.fillRect(img.rect(), QColor(color)) + qp.end() + return QIcon(img) \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/widgets/po_styling_options.py b/src/SAGisXPlanung/gui/widgets/po_styling_options.py new file mode 100644 index 0000000..9ed52a6 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/po_styling_options.py @@ -0,0 +1,82 @@ + +from qgis.PyQt.QtWidgets import (QWidget, QGridLayout, QLabel, QVBoxLayout, QHBoxLayout, QSlider, QSizePolicy, + QSpacerItem, QLineEdit, QDial) +from qgis.PyQt.QtCore import Qt, pyqtSlot, pyqtSignal + + +class QCommonStylingOptions(QWidget): + + ATTRIBUTE_SIZE = 'skalierung' + ATTRIBUTE_ANGLE = 'drehwinkel' + + styleChanged = pyqtSignal(str, object) # style attribute, value + + def __init__(self, parent=None): + super(QCommonStylingOptions, self).__init__(parent) + + self._layout = QGridLayout() + self._layout.setContentsMargins(0, 0, 0, 0) + + self._layout.addWidget(QLabel('Größe'), 0, 0) + self._layout.addWidget(QLabel('Drehwinkel'), 1, 0) + + vbox = QVBoxLayout() + self.sizeSlider = QSlider(Qt.Horizontal, self) + self.sizeSlider.setRange(1, 99) + self.sizeSlider.setSliderPosition(50) + self.sizeSlider.setSingleStep(1) + vbox.addWidget(self.sizeSlider) + + hbox = QHBoxLayout() + hbox.addWidget(QLabel('Klein')) + hbox.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)) + hbox.addWidget(QLabel('Mittel')) + hbox.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)) + hbox.addWidget(QLabel('Groß')) + vbox.addLayout(hbox) + self._layout.addLayout(vbox, 0, 1) + + hbox = QHBoxLayout() + self.angleEdit = QLineEdit() + self.angleDial = QDial() + self.angleDial.setRange(0, 359) + self.angleDial.setMaximumWidth(40) + self.angleDial.setMaximumHeight(40) + hbox.addWidget(self.angleEdit) + hbox.addWidget(self.angleDial) + self._layout.addLayout(hbox, 1, 1) + + self.setLayout(self._layout) + + # listeners + self.angleEdit.editingFinished.connect(self.onAngleTextEdited) + self.angleDial.valueChanged.connect(self.onDialValueChanged) + self.sizeSlider.valueChanged.connect(self.onSizeSliderChanged) + + def setSize(self, scale: float): + self.sizeSlider.setValue(scale * (99 - 1) + 1) + + def setAngle(self, angle: int): + if angle > 360 or angle < 0: + raise ValueError('Angle has to be between 0 and 360') + self.angleEdit.setText(f'{angle}°') + self.angleDial.setValue(angle) + + @pyqtSlot() + def onAngleTextEdited(self): + text = self.angleEdit.text().replace('°', '') + self.angleDial.setValue(int(text)) + + @pyqtSlot(int) + def onDialValueChanged(self, value: int): + self.angleEdit.setText(f'{value}°') + + self.styleChanged.emit(self.ATTRIBUTE_ANGLE, value) + + @pyqtSlot(int) + def onSizeSliderChanged(self, value: int): + scale = (value - 1) / (99 - 1) + self.styleChanged.emit(self.ATTRIBUTE_SIZE, scale) + + + diff --git a/src/SAGisXPlanung/gui/widgets/selected_geoms_view.py b/src/SAGisXPlanung/gui/widgets/selected_geoms_view.py new file mode 100644 index 0000000..aa73460 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/selected_geoms_view.py @@ -0,0 +1,119 @@ +import logging + +import qasync + +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import (QAbstractItemModel, Qt, QModelIndex, QPoint, QAbstractProxyModel, QSortFilterProxyModel, + pyqtSlot, QPersistentModelIndex, QItemSelection, QSize) +from qgis.PyQt.QtWidgets import (QTreeView) +from qgis.gui import QgsHighlight +from qgis.core import QgsVectorLayer +from qgis.utils import iface + +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui.style import TagStyledDelegate, HighlightRowProxyStyle + +logger = logging.getLogger(__name__) + + +class QSelectedGeometriesView(QTreeView): + + def __init__(self, data, layer: QgsVectorLayer, parent=None): + super(QSelectedGeometriesView, self).__init__(parent) + self.model = SelectedGeometriesTableModel(data) + self.setModel(self.model) + + self.layer = layer + self.highlight = None + + self.setItemDelegate(TagStyledDelegate()) + self.proxy_style = HighlightRowProxyStyle('Fusion') + self.proxy_style.setParent(self) + self.setStyle(self.proxy_style) + self.setMouseTracking(True) + + self.entered.connect(self.onItemHovered) + + def sizeHint(self): + size = super(QSelectedGeometriesView, self).sizeHint() + return QSize(size.width(), 80) + + def leaveEvent(self, *args, **kwargs): + self.delete_highlight() + super(QSelectedGeometriesView, self).leaveEvent(*args, **kwargs) + + def geometries(self): + feat_ids = [self.model.data(self.model.index(i, 0)) for i in range(self.model.rowCount(QModelIndex()))] + geometries = [self.layer.getGeometry(fid) for fid in feat_ids] + + return geometries + + def itemCount(self) -> int: + return self.model.rowCount(QModelIndex()) + + @qasync.asyncSlot(QModelIndex) + async def onItemHovered(self, index: QModelIndex): + if not index.isValid(): + return + + feat_id = self.model._data[index.row()][0] + feat = self.layer.getFeature(feat_id) + + self.delete_highlight() + self.highlight = QgsHighlight(iface.mapCanvas(), feat, self.layer) + self.highlight.setColor(QColor(255, 0, 0, 125)) + self.highlight.setBuffer(0.1) + self.highlight.show() + + def delete_highlight(self): + if not self.highlight: + return + + self.highlight.hide() + iface.mapCanvas().scene().removeItem(self.highlight) + self.highlight = None + + +class SelectedGeometriesTableModel(QAbstractItemModel): + def __init__(self, data): + super(SelectedGeometriesTableModel, self).__init__() + self._data = data + self._horizontal_header = ['Feature-Id'] + + def addChild(self, data, _parent=QModelIndex()): + row = self.rowCount(_parent) + self.beginInsertRows(_parent, row, row) + self._data.append(data) + self.endInsertRows() + + def removeRows(self, row: int, count: int, parent=QModelIndex()) -> bool: + self.beginRemoveRows(parent, row, row + count - 1) + del self._data[row: row + count] + self.endRemoveRows() + return True + + def data(self, index, role=Qt.DisplayRole): + if role == Qt.DisplayRole: + value = self._data[index.row()][index.column()] + return value + + def index(self, row, column, _parent=QModelIndex()): + return self.createIndex(row, column) + + def parent(self, index): + return QModelIndex() + + def headerData(self, col, orientation, role): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self._horizontal_header[col] + + def hasChildren(self, index=QModelIndex()): + return self.rowCount(index) > 0 + + def rowCount(self, index): + if index.isValid(): + return 0 + return len(self._data) + + def columnCount(self, index): + return 1 diff --git a/src/SAGisXPlanung/gui/widgets/svg_symbol_edit.py b/src/SAGisXPlanung/gui/widgets/svg_symbol_edit.py new file mode 100644 index 0000000..189cd98 --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/svg_symbol_edit.py @@ -0,0 +1,219 @@ +import os +from pathlib import Path + +import qasync +from qgis.PyQt.QtGui import QIcon, QMouseEvent +from qgis.PyQt.QtCore import pyqtSignal, Qt +from qgis.PyQt.QtWidgets import (QGroupBox, QHBoxLayout, QVBoxLayout, QLabel, QWidget, QToolButton, QSizePolicy, + QDialog, QDialogButtonBox) +from qgis.core import QgsApplication + +from qgis.gui import QgsSvgSelectorWidget + +from SAGisXPlanung import BASE_DIR +from SAGisXPlanung.config import SVG_CONFIG + + +class SVGSymbolDisplayWidget(QGroupBox): + + svg_selection_saved = pyqtSignal(str) # path + + def __init__(self, svg_path, parent=None): + + super(SVGSymbolDisplayWidget, self).__init__('Symbol') + + self._layout = QHBoxLayout() + self.label_layout = QVBoxLayout() + + self.svg_widget = SymbolEditButton(QIcon(svg_path)) + self.svg_widget.svg_selected.connect(self.onSvgSelected) + self.svg_widget.svg_selection_saved.connect(self.onSvgSelectionSaved) + self.svg_widget.svg_selection_rejected.connect(self.onSvgSelectionRejected) + + file_name = Path(svg_path).name + symbol_node = SVG_CONFIG.get(file_name, "") + self.current_name = symbol_node['name'] if symbol_node else 'Fehlerhaftes Symbol' + self.current_category = symbol_node['category'] if symbol_node else '' + self.label_name = QLabel(self.current_name) + self.label_name.setObjectName('name') + self.category_name = QLabel(self.current_category) + self.category_name.setObjectName('category') + + self.label_layout.addWidget(self.category_name) + self.label_layout.addWidget(self.label_name) + + self._layout.addWidget(self.svg_widget) + self._layout.addItem(self.label_layout) + self._layout.setSpacing(25) + self.setLayout(self._layout) + + self.setStyleSheet(''' + QToolButton { + border: 0px; + } + #category { + text-transform: uppercase; + font-weight: 400; + color: #374151; + } + #name { + font-size: 1.125rem; + font-weight: 500; + color: #1c1917; + } + ''') + + @qasync.asyncSlot() + async def onSvgSelectionRejected(self): + self.svg_widget.button().setIcon(self.svg_widget.icon) + self.category_name.setText(self.current_category) + self.label_name.setText(self.current_name) + + @qasync.asyncSlot(str) + async def onSvgSelectionSaved(self, path): + file_name = Path(path).name + symbol_node = SVG_CONFIG.get(file_name, "") + if not symbol_node: + return + self.current_name = symbol_node['name'] + self.current_category = symbol_node['category'] + self.svg_selection_saved.emit(path) + + @qasync.asyncSlot(str) + async def onSvgSelected(self, path: str): + file_name = Path(path).name + symbol_node = SVG_CONFIG.get(file_name, "") + if not symbol_node: + return + + icon = QIcon(path) + self.svg_widget.button().setIcon(icon) + + self.category_name.setText(symbol_node['category']) + self.label_name.setText(symbol_node['name']) + + +class SymbolEditButton(QWidget): + + svg_selected = pyqtSignal(str) # path + svg_selection_saved = pyqtSignal(str) # path + svg_selection_rejected = pyqtSignal() + + def __init__(self, icon): + super(SymbolEditButton, self).__init__() + self.icon = icon + + self.setLayout(QVBoxLayout()) + self.setMouseTracking(True) + self.setCursor(Qt.PointingHandCursor) + self.setObjectName('back') + self.setAttribute(Qt.WA_StyledBackground, True) + self.layout().setContentsMargins(5, 5, 5, 5) + + self._button = QToolButton() + self._button.setIcon(self.icon) + self._button.setAttribute(Qt.WA_TransparentForMouseEvents) + self._button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) + self._button.setMinimumHeight(20) + self.setMinimumHeight(20) + self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) + + self.layout().addWidget(self._button, 0, Qt.AlignCenter) + + self.setStyleSheet(''' + #back:hover { + background-color: #bfdbfe; + border: none; + border-radius: 3px; + } + ''') + + def mouseReleaseEvent(self, event: QMouseEvent): + if event.button() == Qt.LeftButton: + svg_dialog = QSymbolSelectionDialog(self) + svg_dialog.svg_selected.connect(self.svg_selected.emit) + svg_dialog.svg_selection_saved.connect(self.onSvgSelectionSaved) + svg_dialog.rejected.connect(self.svg_selection_rejected.emit) + svg_dialog.show() + super(SymbolEditButton, self).mouseReleaseEvent(event) + + @qasync.asyncSlot(str) + async def onSvgSelectionSaved(self, path): + self.icon = QIcon(os.path.join(BASE_DIR, path)) + self.svg_selection_saved.emit(path) + + def button(self): + return self._button + + +class QSymbolSelectionDialog(QDialog): + + svg_selected = pyqtSignal(str) # path + svg_selection_saved = pyqtSignal(str) # path + + def __init__(self, parent): + + super(QSymbolSelectionDialog, self).__init__(parent) + + self.setWindowTitle('Symbol wählen') + + self._layout = QVBoxLayout() + + self.previous_svg_path = QgsApplication.svgPaths() + QgsApplication.setSvgPaths([os.path.join(BASE_DIR, 'symbole')]) + QgsApplication.setDefaultSvgPaths([os.path.join(BASE_DIR, 'symbole')]) + + self.svg_widget = QgsSvgSelectorWidget() + self.svg_widget.sourceLineEdit().setVisible(False) + self.svg_widget.setSvgPath(os.path.join(BASE_DIR, 'symbole')) + self.svg_widget.svgSelected.connect(self.onSvgSelected) + self._layout.addWidget(self.svg_widget) + + self.nav_layout = QHBoxLayout() + self.nav_layout.setSpacing(10) + self.buttons = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel) + self.buttons.accepted.connect(self.accept) + self.buttons.rejected.connect(self.reject) + self.error_label = QLabel('') + self.error_label.setObjectName('error-label') + self.nav_layout.addWidget(self.error_label) + self.nav_layout.addWidget(self.buttons) + self._layout.addItem(self.nav_layout) + + self.setLayout(self._layout) + + self.setStyleSheet(''' + #error-label { + font-weight: bold; + font-size: 7pt; + color: #991B1B; + } + ''') + + def closeEvent(self, e): + QgsApplication.setDefaultSvgPaths(self.previous_svg_path) + QgsApplication.setSvgPaths(self.previous_svg_path) + + @qasync.asyncSlot(str) + async def onSvgSelected(self, path: str): + file_name = Path(path).name + symbol_node = SVG_CONFIG.get(file_name, "") + if not symbol_node: + self.error_label.setText('Dieses Symbol gehört nicht zum Symbolkatalog von SAGis XPlanung!') + self.buttons.button(QDialogButtonBox.Save).setEnabled(False) + return + + self.buttons.button(QDialogButtonBox.Save).setEnabled(True) + self.error_label.clear() + self.svg_selected.emit(path) + + def accept(self): + file_name = Path(self.svg_widget.currentSvgPath()).name + symbol_node = SVG_CONFIG[file_name] + + self.svg_selection_saved.emit(os.path.join('symbole', symbol_node['category'], file_name)) + + super(QSymbolSelectionDialog, self).accept() + + def closeEvent(self, event): + super(QSymbolSelectionDialog, self).closeEvent(event) \ No newline at end of file diff --git a/src/SAGisXPlanung/gui/widgets/template_edit.py b/src/SAGisXPlanung/gui/widgets/template_edit.py new file mode 100644 index 0000000..28f157e --- /dev/null +++ b/src/SAGisXPlanung/gui/widgets/template_edit.py @@ -0,0 +1,195 @@ +import os + +from qgis.PyQt.QtWidgets import (QWidget, QVBoxLayout, QGridLayout, QComboBox, QGroupBox, QHBoxLayout, QToolButton, + QSizePolicy, QSpacerItem, QButtonGroup, QLabel) +from qgis.PyQt.QtCore import pyqtSlot, pyqtSignal, Qt +from qgis.PyQt.QtGui import QIcon, QMouseEvent + +from SAGisXPlanung import BASE_DIR +from SAGisXPlanung.BuildingTemplateItem import BuildingTemplateCellDataType +from .po_styling_options import QCommonStylingOptions + + +class QBuildingTemplateEdit(QWidget): + + cellDataChanged = pyqtSignal(BuildingTemplateCellDataType, int) + rowCountChanged = pyqtSignal(int, list) # row count, cell types + styleChanged = pyqtSignal(str, object) # style attribute, value + + def __init__(self, cells, rows, scale=0.5, angle=0, parent=None): + super(QBuildingTemplateEdit, self).__init__(parent) + + self.cells = cells + self.columns = 2 + self.rows = rows + + self._layout = QVBoxLayout() + self.template_form = SelectTemplateFormWidget(default_rows=self.rows) + self.template_form.rowCountChanged.connect(self.onRowCountChanged) + + self._layout.addWidget(self.template_form) + + self.template_grid_container = QGroupBox('Zellwerte der Nutzungschablone') + self.template_grid = QGridLayout() + self.buildTemplateGrid() + self._layout.addWidget(self.template_grid_container) + + self.common_style_container = QGroupBox('Allgemeine Darstellungsoptionen') + self.common_style_container.setLayout(QVBoxLayout()) + self.common_styling_opt = QCommonStylingOptions() + self.common_styling_opt.setSize(scale) + self.common_styling_opt.setAngle(angle) + self.common_styling_opt.styleChanged.connect(lambda k, v: self.styleChanged.emit(k, v)) + self.common_style_container.layout().addWidget(self.common_styling_opt) + + self._layout.addItem((QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Expanding))) + self._layout.addWidget(self.common_style_container) + + self.setLayout(self._layout) + + def buildTemplateGrid(self): + # hackish solution to clear layout, transfer to new widget which will be destroyed automatically + # when out it goes of scope: https://stackoverflow.com/a/7082920/12690772 + QWidget().setLayout(self.template_grid) + + self.template_grid = QGridLayout() + for i in range(self.rows): + for j in range(self.columns): + index = i * self.columns + j % self.columns + + cb = QComboBox() + cb.setProperty('template-index', index) + for x in BuildingTemplateCellDataType: + cb.addItem(x.value, x.name) + index = cb.findData(self.cells[index].name) + if index >= 0: + cb.setCurrentIndex(index) + + cb.currentIndexChanged.connect(self.onComboBoxIndexChanged) + + self.template_grid.addWidget(cb, i, j) + + self.template_grid_container.setLayout(self.template_grid) + + @pyqtSlot(int) + def onComboBoxIndexChanged(self, index: int): + cell_index = self.sender().property('template-index') + item_value = self.sender().itemData(index) + item = BuildingTemplateCellDataType[item_value] + self.cellDataChanged.emit(item, cell_index) + + @pyqtSlot(int) + def onRowCountChanged(self, row_count: int): + row_difference = self.rows - row_count + if row_difference > 0: + del self.cells[-1*abs(row_difference)*self.columns:] + elif row_difference < 0: + all_cell_types = set([x for x in BuildingTemplateCellDataType]) + unused_cell_types = [x for x in all_cell_types if x not in self.cells] + for i in range(abs(row_difference)*self.columns): + self.cells.append(unused_cell_types[i]) + self.rows = row_count + self.rowCountChanged.emit(self.rows, self.cells) + + self.buildTemplateGrid() + + +class SelectTemplateFormWidget(QGroupBox): + + rowCountChanged = pyqtSignal(int) + + def __init__(self, default_rows=None, parent=None): + + super(SelectTemplateFormWidget, self).__init__('Form der Nutzungschablone') + + self._layout = QHBoxLayout() + + self.button_small = LabeledButton(QIcon(os.path.join(BASE_DIR, 'gui/resources/grid2x2.svg')), '2 x 2', 2) + self.button_medium = LabeledButton(QIcon(os.path.join(BASE_DIR, 'gui/resources/grid2x3.svg')), '2 x 3', 3) + self.button_large = LabeledButton(QIcon(os.path.join(BASE_DIR, 'gui/resources/grid2x4.svg')), '2 x 4', 4) + self.button_small.buttonToggled.connect(self.onButtonToggled) + self.button_medium.buttonToggled.connect(self.onButtonToggled) + self.button_large.buttonToggled.connect(self.onButtonToggled) + + self._buttons = [self.button_small, self.button_medium, self.button_large] + + if default_rows == 2: + self.button_small.toggleButton(True) + elif default_rows == 3: + self.button_medium.toggleButton(True) + elif default_rows == 4: + self.button_large.toggleButton(True) + + self._layout.addWidget(self.button_small) + self._layout.addWidget(self.button_medium) + self._layout.addWidget(self.button_large) + self._layout.setSpacing(25) + self.setLayout(self._layout) + + self.setStyleSheet(''' + QToolButton { + border: 0px; + } + ''') + + @pyqtSlot(bool, int) + def onButtonToggled(self, checked: bool, row_count: int): + if not checked: + return + + self.rowCountChanged.emit(row_count) + + for b in self._buttons: + if b == self.sender(): + continue + b.toggleButton(False) + + +class LabeledButton(QWidget): + + buttonToggled = pyqtSignal(bool, int) # checked, row count + + def __init__(self, icon, label_text, row_count): + super(LabeledButton, self).__init__() + self.row_count = row_count + + self.setLayout(QVBoxLayout()) + self.setMouseTracking(True) + self.setCursor(Qt.PointingHandCursor) + self.setObjectName('back') + self.setAttribute(Qt.WA_StyledBackground, True) + self.layout().setContentsMargins(5, 5, 5, 5) + + self._button = QToolButton() + self._button.setIcon(icon) + self._button.setAttribute(Qt.WA_TransparentForMouseEvents) + + self.layout().addWidget(self._button, 0, Qt.AlignCenter) + self.layout().addWidget(QLabel(label_text), 0, Qt.AlignCenter) + + self.setStyleSheet(''' + #back:hover { + background-color: #E5E7EB; + border: none; + border-radius: 3px; + } + #back[checked=true] { + border: 1px solid #1D4ED8; + border-radius: 3px; + } + ''') + + def toggleButton(self, checked: bool): + self.setProperty('checked', checked) + self.style().unpolish(self) + self.style().polish(self) + self.update() + self.buttonToggled.emit(checked, self.row_count) + + def mouseReleaseEvent(self, event: QMouseEvent): + if event.button() == Qt.LeftButton and not self.property('checked'): + self.toggleButton(True) + super(LabeledButton, self).mousePressEvent(event) + + def button(self): + return self._button diff --git a/src/SAGisXPlanung/metadata.txt b/src/SAGisXPlanung/metadata.txt new file mode 100644 index 0000000..5906a56 --- /dev/null +++ b/src/SAGisXPlanung/metadata.txt @@ -0,0 +1,49 @@ +# This file contains metadata for your plugin. + +# This file should be included when you package your plugin.# Mandatory items: + +[general] +name=SAGis XPlanung +qgisMinimumVersion=3.22 +description=Plugin zur XPlanung-konformen Erfassung und Verwaltung von Bauleitplänen +version=2.2.0 +author=NTI Deutschland GmbH +email=qgis-de@nti.biz + +about=DE: SAGis XPlanung ermöglicht die digitale Erfassung von Planwerken der Bauleitplanung unter Verwendung des Standards XPlanung. + + EN: The plugin supports creation of datasets for German land use planning standard XPlanung. + +tracker=https://github.com/nti-de/SAGisXPlanung/issues +repository=https://github.com/nti-de/SAGisXPlanung/ +# End of mandatory metadata + +# Recommended items: + +hasProcessingProvider=yes +# Uncomment the following line and add your changelog: +# changelog= + +# Tags are comma separated with spaces allowed +tags=xplanung, bplan, fnp, bebauungsplan + +homepage=https://www.nti.biz/de/ +category=Plugins +icon=gui/resources/sagis_icon.png +# experimental flag +experimental=False + +# deprecated flag (applies to the whole plugin, not just a single version) +deprecated=False + +# Since QGIS 3.8, a comma separated list of plugins to be installed +# (or upgraded) can be specified. +# Check the documentation for more information. +# plugin_dependencies= + +Category of the plugin: Raster, Vector, Database or Web +# category= + +# If the plugin can run on QGIS Server. +server=False + diff --git a/src/SAGisXPlanung/pb_tool.cfg b/src/SAGisXPlanung/pb_tool.cfg new file mode 100644 index 0000000..841fa48 --- /dev/null +++ b/src/SAGisXPlanung/pb_tool.cfg @@ -0,0 +1,58 @@ +[plugin] +# Name of the plugin. This is the name of the directory that will +# be created in .qgis2/python/plugins +name: SAGisXPlanung + +# Full path to where you want your plugin directory copied. If empty, +# the QGIS default path will be used. Don't include the plugin name in +# the path. +#plugin_path: %%APPDATA%%\QGIS\QGIS3\profiles\default\python\plugins +plugin_path: C:\Users\wilhelm\AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins + +[files] +# Python files that should be deployed with the plugin +python_files: __init__.py XPlanungPlugin.py ConverterTasks.py utils.py formUtils.py MapLayerRegistry.py RuleBasedSymbolRenderer.py Settings.py BuildingTemplateItem.py XPlanungItem.py + +# The main dialog file that is loaded (not compiled) +main_dialog: + +# Other ui files for dialogs you create (these will be compiled) +compiled_ui_files: + +# Resource file(s) that will be compiled +resource_files: + +# Other files required for the plugin +extras: metadata.txt .env + +# Other directories to be deployed with the plugin. +# These must be subdirectories under the plugin directory +extra_dirs: + ext + ui + symbole + gui + Tools + XPlan + BPlan + FPlan + RPlan + LPlan + SonstigePlanwerke + GML + processing + config + database + +# ISO code(s) for any locales (translations), separated by spaces. +# Corresponding .ts files must exist in the i18n directory +locales: + +[help] +# the built help directory that should be deployed with the plugin +dir: help/build/html +# the name of the directory to target in the deployed plugin +target: help + + + diff --git a/src/SAGisXPlanung/processing/__init__.py b/src/SAGisXPlanung/processing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/SAGisXPlanung/processing/export_all.py b/src/SAGisXPlanung/processing/export_all.py new file mode 100644 index 0000000..c9629df --- /dev/null +++ b/src/SAGisXPlanung/processing/export_all.py @@ -0,0 +1,164 @@ +from enum import Enum +from pathlib import Path + +from qgis.core import (QgsProcessingAlgorithm, QgsProcessingParameterFolderDestination, QgsProcessingParameterEnum) + +from SAGisXPlanung import Session +from SAGisXPlanung.GML.GMLWriter import GMLWriter +from SAGisXPlanung.utils import CLASSES + + +class XPlanungExportTypes(Enum): + + XP_Plan = 'Alle Objektklassen' + BP_Plan = 'Bebauungspläne' + FP_Plan = 'Flächennutzungspläne' + RP_Plan = 'Regionalpläne' + LP_Plan = 'Landschaftspläne' + + +class ExportAllAlgorithm(QgsProcessingAlgorithm): + """ + Verarbeitungswerkzeug zum Exportieren aller Pläne in der Datenbank + """ + + # Constants used to refer to parameters and outputs. They will be + # used when calling the algorithm from another algorithm, or when + # calling from the QGIS console. + + OUTPUT_FOLDER = 'OUTPUT_FOLDER' + OUTPUT_FORMAT = 'OUTPUT_FORMAT' + XPLANUNG_TYPES = 'XPLANUNG_TYPES' + + def createInstance(self): + return ExportAllAlgorithm() + + def name(self): + """ + Returns the algorithm name, used for identifying the algorithm. This + string should be fixed for the algorithm, and must not be localised. + The name should be unique within each provider. Names should contain + lowercase alphanumeric characters only and no spaces or other + formatting characters. + """ + return 'exportall' + + def displayName(self): + """ + Returns the translated algorithm name, which should be used for any + user-visible display of the algorithm name. + """ + return 'Bauleitpläne exportieren' + + def group(self): + """ + Returns the name of the group this algorithm belongs to. This string + should be localised. + """ + return 'Export' + + def groupId(self): + """ + Returns the unique ID of the group this algorithm belongs to. This + string should be fixed for the algorithm, and must not be localised. + The group id should be unique within each provider. Group id should + contain lowercase alphanumeric characters only and no spaces or other + formatting characters. + """ + return 'sagis-xplanung-export' + + def shortHelpString(self): + """ + Returns a localised short helper string for the algorithm. This string + should provide a basic description about what the algorithm does and the + parameters and outputs associated with it.. + """ + return 'Alle in der SAGis XPlanung-Datenbank erfassten Pläne als XPlanGML exportieren' + + def initAlgorithm(self, config=None): + """ + Here we define the inputs and output of the algorithm, along + with some other properties. + """ + self.addParameter( + QgsProcessingParameterFolderDestination( + self.OUTPUT_FOLDER, + 'Ausgabe Ordner', + ) + ) + + self.addParameter( + QgsProcessingParameterEnum( + self.XPLANUNG_TYPES, + 'Objektklassen', + options=[x.value for x in XPlanungExportTypes], + defaultValue=XPlanungExportTypes.XP_Plan + ) + ) + + self.addParameter( + QgsProcessingParameterEnum( + self.OUTPUT_FORMAT, + 'Ausgabeformat', + options=['ZIP-Archiv', 'XPlanGML'], + defaultValue='ZIP-Archiv' + ) + ) + + def processAlgorithm(self, parameters, context, feedback): + feedback.pushInfo('Start process') + + output_path = self.parameterAsFile(parameters, self.OUTPUT_FOLDER, context) + Path(output_path).mkdir(parents=True, exist_ok=True) + + if not output_path: + feedback.reportError('Ausgabeordner nicht gefunden', True) + return {self.OUTPUT_FOLDER: output_path} + + export_format = self.parameterAsInt(parameters, self.OUTPUT_FORMAT, context) + + export_type = self.parameterAsInt(parameters, self.XPLANUNG_TYPES, context) + feedback.pushInfo(str([x.value for x in XPlanungExportTypes][export_type])) + export_type_name = [x.name for x in XPlanungExportTypes][export_type] + export_cls_type = CLASSES[export_type_name] + + feedback.pushDebugInfo(f'XPlanung Typ: {export_cls_type.__name__}') + + with Session.begin() as session: + all_plans = session.query(export_cls_type).all() + count = len(all_plans) + + for i, plan in enumerate(all_plans): + try: + writer = GMLWriter(plan) + file_name = plan.name.replace("/", "-").replace('"', '\'') + if export_format == 0: + buffer = writer.toArchive() + with open(f'{output_path}/{file_name}.zip', 'wb') as f: + f.write(buffer.getvalue()) + elif export_format == 1: + gml = writer.toGML() + with open(f'{output_path}/{file_name}.gml', 'wb') as f: + f.write(gml) + except ValueError as e: + feedback.pushWarning(f'{plan.name} konnte nicht exportiert werden') + feedback.pushWarning(str(e)) + continue + finally: + prog = self.translateProgress(i, 0, count, 0, 100) + feedback.setProgress(prog) + + feedback.pushInfo(f'{plan.name}') + + return {self.OUTPUT_FOLDER: output_path} + + def translateProgress(self, value, left_min, left_max, right_min, right_max): + # Figure out how 'wide' each range is + leftSpan = left_max - left_min + rightSpan = right_max - right_min + + # Convert the left range into a 0-1 range (float) + valueScaled = float(value - left_min) / float(leftSpan) + + # Convert the 0-1 range into a value in the right range. + return right_min + (valueScaled * rightSpan) \ No newline at end of file diff --git a/src/SAGisXPlanung/processing/import_civil.py b/src/SAGisXPlanung/processing/import_civil.py new file mode 100644 index 0000000..f8c15cc --- /dev/null +++ b/src/SAGisXPlanung/processing/import_civil.py @@ -0,0 +1,505 @@ +import uuid +from dataclasses import dataclass, field +from typing import List + +from geoalchemy2 import WKTElement +from qgis.core import (QgsProcessingAlgorithm, QgsProcessingParameterFile, QgsProcessingException) +from qgis.utils import iface +from shapely import wkt +from shapely.geometry import Polygon, MultiPolygon +from shapely.validation import explain_validity +from sqlalchemy import create_engine, text +from sqlalchemy.event import listen + +from SAGisXPlanung.BPlan.BP_Basisobjekte.enums import BP_PlanArt, BP_Rechtscharakter +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Plan, BP_Bereich +from SAGisXPlanung.BPlan.BP_Bebauung.enums import BP_BebauungsArt +from SAGisXPlanung.BPlan.BP_Bebauung.feature_types import BP_BaugebietsTeilFlaeche, BP_BauGrenze, BP_BauLinie +from SAGisXPlanung.BPlan.BP_Gemeinbedarf_Spiel_und_Sportanlagen.feature_types import (BP_GemeinbedarfsFlaeche, + BP_SpielSportanlagenFlaeche) +from SAGisXPlanung.BPlan.BP_Landwirtschaft_Wald_und_Gruenflaechen.feature_types import BP_GruenFlaeche +from SAGisXPlanung.BPlan.BP_Naturschutz_Landschaftsbild_Naturhaushalt.feature_types import (BP_AnpflanzungBindungErhaltung, + BP_SchutzPflegeEntwicklungsFlaeche) +from SAGisXPlanung.BPlan.BP_Ver_und_Entsorgung.feature_types import BP_VerEntsorgung +from SAGisXPlanung.BPlan.BP_Verkehr.enums import BP_ZweckbestimmungStrassenverkehr +from SAGisXPlanung.BPlan.BP_Verkehr.feature_types import BP_StrassenVerkehrsFlaeche, BP_StrassenbegrenzungsLinie, \ + BP_VerkehrsflaecheBesondererZweckbestimmung +from SAGisXPlanung.BPlan.BP_Wasser.feature_types import BP_GewaesserFlaeche +from SAGisXPlanung.XPlan.data_types import XP_Gemeinde +from SAGisXPlanung.XPlan.enums import (XP_BedeutungenBereich, XP_ZweckbestimmungVerEntsorgung, XP_ZweckbestimmungGruen, + XP_AllgArtDerBaulNutzung, XP_ABEMassnahmenTypen, + XP_AnpflanzungBindungErhaltungsGegenstand, + XP_BesondereArtDerBaulNutzung, XP_ZweckbestimmungGemeinbedarf, + XP_ZweckbestimmungSpielSportanlage, XP_ZweckbestimmungGewaesser) +from SAGisXPlanung.XPlan.feature_types import XP_Objekt +from SAGisXPlanung.gui.widgets.QPlanComboBox import QPlanComboBox +from SAGisXPlanung.utils import query_existing, save_to_db + + +class ImportCivil3DAlgorithm(QgsProcessingAlgorithm): + """ + Verarbeitungswerkzeug zum Importieren von Planwerken aus Civil3D + """ + + INPUT_FILE = 'INPUT_FILE' + + def createInstance(self): + return ImportCivil3DAlgorithm() + + def name(self): + """ + Returns the algorithm name, used for identifying the algorithm. This + string should be fixed for the algorithm, and must not be localised. + The name should be unique within each provider. Names should contain + lowercase alphanumeric characters only and no spaces or other + formatting characters. + """ + return 'importcivil3d' + + def displayName(self): + """ + Returns the translated algorithm name, which should be used for any + user-visible display of the algorithm name. + """ + return 'Aus Civil3D importieren (.sqlite)' + + def group(self): + """ + Returns the name of the group this algorithm belongs to. This string + should be localised. + """ + return 'Import' + + def groupId(self): + """ + Returns the unique ID of the group this algorithm belongs to. This + string should be fixed for the algorithm, and must not be localised. + The group id should be unique within each provider. Group id should + contain lowercase alphanumeric characters only and no spaces or other + formatting characters. + """ + return 'sagis-xplanung-import' + + def shortHelpString(self): + """ + Returns a localised short helper string for the algorithm. This string + should provide a basic description about what the algorithm does and the + parameters and outputs associated with it.. + """ + return 'Ein Planwerk aus Civil3D in die XPlanung Datenbank einlesen.' + + def initAlgorithm(self, config=None): + self.addParameter( + QgsProcessingParameterFile( + self.INPUT_FILE, + 'Eingabedatei (.sqlite)', + extension='sqlite' + ) + ) + + def processAlgorithm(self, parameters, context, feedback): + """ + Here is where the processing itself takes place. + """ + input_path = self.parameterAsString(parameters, self.INPUT_FILE, context) + + if not input_path: + feedback.reportError('Eingabedatei nicht gefunden', True) + return {} + + engine = create_engine(f'sqlite:///{input_path}') + listen(engine, 'connect', load_spatialite) + + with engine.connect() as conn: + + spatiallite_version = conn.execute(text("SELECT spatialite_version() as version;")).scalar_one() + feedback.pushInfo(f"Spatialite Version: {spatiallite_version}") + + plan_ids = conn.execute(text("SELECT id FROM civil_plan")) + for row in plan_ids: + feedback.pushInfo(f"Plan mit id {row.id} wird importiert...") + plan = self.processPlan(row.id, conn, feedback) + + save_to_db(plan) + + return {} + + def processPlan(self, plan_xid, conn, feedback) -> BP_Plan: + plan = BP_Plan() + plan.id = plan_xid + + s = text("SELECT art, gemeinde_id, name FROM civil_plan WHERE id = :xid") + res = conn.execute(s, {"xid": plan_xid}).first() + + gemeinde = self.processGemeinde(res.gemeinde_id, conn, feedback) + plan.gemeinde.append(gemeinde) + + plan.name = res.name + plan.planArt = BP_PlanArt[res.art] + + s = text("SELECT ST_AsText(geom) AS g, ST_SRID(geom) AS srid FROM civil_line WHERE plan_id = :xid AND layer LIKE '%Geltungsbereich%'") + res = conn.execute(s, {"xid": plan_xid}).first() + + geometry = wkt.loads(res.g) + if not geometry.is_valid: + raise QgsProcessingException(f'Geometrie ist nicht gültig: {explain_validity(geometry)}') + + geltungsbereich_polygons = [] + for line in geometry.geoms: + if not line.is_ring: + raise QgsProcessingException(f'Geltungsbereich ist kein geschlossener Umring') + + geltungsbereich_polygons.append(Polygon(line)) + + geltungsbereich_geom = MultiPolygon(geltungsbereich_polygons) + plan.raeumlicherGeltungsbereich = WKTElement(geltungsbereich_geom.wkt, srid=res.srid) + + bereich = BP_Bereich() + bereich.id = uuid.uuid4() + bereich.name = 'Geltungsbereich' + bereich.nummer = '0' + bereich.bedeutung = XP_BedeutungenBereich.Teilbereich + bereich.geltungsbereich = plan.raeumlicherGeltungsbereich + + mappings = {"civil_area": CIVIL_STYLE_AREAS, "civil_point": CIVIL_STYLE_POINTS, "civil_line": CIVIL_STYLE_LINES} + for table, mapper in mappings.items(): + s = text(f"SELECT id FROM {table} WHERE plan_id = :xid") + res = conn.execute(s, {"xid": plan_xid}) + for row in res: + feedback.pushDebugInfo(f"Verarbeitung des Planinhalts: {row.id}") + planinhalt = self.processPlaninhalt(row.id, table, mapper, conn, feedback) + if planinhalt: + bereich.planinhalt.append(planinhalt) + + plan.bereich.append(bereich) + + return plan + + def processPlaninhalt(self, planinhalt_xid, table, mapper, conn, feedback) -> XP_Objekt: + s = text(f"SELECT layer, rechtscharakter, ST_AsText(geom) AS geom, ST_SRID(geom) AS srid FROM {table} WHERE id = :xid") + res = conn.execute(s, {"xid": planinhalt_xid}).first() + + # for each civil-styleid, check if the id is in the layer string, if yes create the corresponding object + for civil_style in mapper: + if civil_style.civil_id not in res.layer: + continue + + # create instance and assign mandatory attributes + bp_objekt = civil_style.createInstance() + bp_objekt.rechtscharakter = BP_Rechtscharakter[res.rechtscharakter] + + # parse and assign geometry + geometry = wkt.loads(res.geom) + if not geometry.is_valid: + raise QgsProcessingException(f'Geometrie ist nicht gültig: {explain_validity(geometry)}') + bp_objekt.position = WKTElement(geometry.wkt, srid=res.srid) + if civil_style.property_table == PARCEL_TABLE: + bp_objekt.flaechenschluss = True + + # parse properties from Civil3D + if civil_style.civil_properties: + s = text(f"SELECT name, value FROM {civil_style.property_table.table_name} WHERE {civil_style.property_table.id_column} = :xid") + res = conn.execute(s, {"xid": planinhalt_xid}) + for row in res: + for civil_prop in civil_style.civil_properties: + if civil_prop.civil_id not in row.name: + continue + + if row.value in (None, ''): + continue + if civil_prop.enum_value: + if row.value == 'True': # note the string comparison here; values from sqlite are string + setattr(bp_objekt, civil_prop.xplanung_attribute, civil_prop.enum_value) + continue + + setattr(bp_objekt, civil_prop.xplanung_attribute, row.value) + + return bp_objekt + + def processGemeinde(self, gemeinde_xid, conn, feedback) -> XP_Gemeinde: + gemeinde = XP_Gemeinde() + gemeinde.id = gemeinde_xid + + s = text("SELECT * FROM civil_gemeinde WHERE id = :xid") + res = conn.execute(s, {"xid": gemeinde_xid}).first() + feedback.pushInfo(f"Gemeindeschlüssel: {res.ags} Gemeindename: {res.gemeindeName}") + + gemeinde.ags = res.ags + gemeinde.rs = res.rs + gemeinde.gemeindeName = res.gemeindeName + gemeinde.ortsteilName = res.ortsteilName + + # find if gemeinde already exists in database + existing_gemeinde = query_existing(gemeinde) + + return existing_gemeinde or gemeinde + + def postProcessAlgorithm(self, context, feedback): + # refresh plan combobox in post-processing + cb = iface.mainWindow().findChild(QPlanComboBox) + if cb is None: + return {} + + prev_id = cb.currentPlanId() + cb.refresh() + cb.setCurrentPlan(prev_id) + + return {} + + def translateProgress(self, value, left_min, left_max, right_min, right_max): + # Figure out how 'wide' each range is + leftSpan = left_max - left_min + rightSpan = right_max - right_min + + # Convert the left range into a 0-1 range (float) + valueScaled = float(value - left_min) / float(leftSpan) + + # Convert the 0-1 range into a value in the right range. + return right_min + (valueScaled * rightSpan) + + +def load_spatialite(connection, _): + connection.enable_load_extension(True) + connection.execute("SELECT load_extension('mod_spatialite')") + connection.enable_load_extension(False) + + +@dataclass +class CivilProperty: + civil_id: str + xplanung_attribute: str + is_array_type: bool = False + enum_value: object = None + + +@dataclass +class CivilPropertyTable: + table_name: str + id_column: str + + +PARCEL_TABLE = CivilPropertyTable("parcel_properties", "parcel_id") +LINE_TABLE = CivilPropertyTable("line_properties", "line_id") +POINT_TABLE = CivilPropertyTable("point_properties", "point_id") + + +@dataclass +class CivilStyle: + civil_id: str + xtype: type + property_table: CivilPropertyTable + civil_properties: List[CivilProperty] = field(default_factory=list) + attributes: dict = field(default_factory=dict) + + def createInstance(self) -> XP_Objekt: + obj = self.xtype() + for attribute, value in self.attributes.items(): + setattr(obj, attribute, value) + return obj + + +CIVIL_PROPERTIES_BEBAUUNG = [ + CivilProperty(civil_id='1_5_Zahl_der_Wohnungen', xplanung_attribute="MaxZahlWohnungen"), + CivilProperty(civil_id='2_1_GFZ_Mindestmass', xplanung_attribute="GFZmin"), + CivilProperty(civil_id='2_1_GFZ_Hoechstmass', xplanung_attribute="GFZmax"), + CivilProperty(civil_id='2_4_BM', xplanung_attribute="BM"), + CivilProperty(civil_id='2_5_GRZ', xplanung_attribute="GRZ"), + CivilProperty(civil_id='2_6_GR', xplanung_attribute="GR"), + CivilProperty(civil_id='2_7_VG_Hoechstmass', xplanung_attribute="Zmax"), + CivilProperty(civil_id='2_7_VG_Mindestmass', xplanung_attribute="Zmin"), + CivilProperty(civil_id='2_7_VG_zwingend', xplanung_attribute="Zzwingend"), + CivilProperty(civil_id='3_1_1_nur_Einzelhaeuser_zulaessig', xplanung_attribute="bebauungsArt", + enum_value=BP_BebauungsArt.Einzelhaeuser, is_array_type=True), + CivilProperty(civil_id='3_1_2_nur_Doppelhaeuser_zulaessig', xplanung_attribute="bebauungsArt", + enum_value=BP_BebauungsArt.Doppelhaeuser), + CivilProperty(civil_id='3_1_3_nur_Hausgruppen_zulaessig', xplanung_attribute="bebauungsArt", + enum_value=BP_BebauungsArt.Hausgruppen), + CivilProperty(civil_id='3_1_4_nur_Einzel_und_Doppelhaeuser_zulaessig', xplanung_attribute="bebauungsArt", + enum_value=BP_BebauungsArt.EinzelhaeuserHausgruppen), +] + +CIVIL_STYLE_AREAS = [ + # Baugebiete + CivilStyle(civil_id='1.1 Wohnbauflächen', xtype=BP_BaugebietsTeilFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, + property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.WohnBauflaeche}), + CivilStyle(civil_id='1.1.1', xtype=BP_BaugebietsTeilFlaeche, civil_properties=CIVIL_PROPERTIES_BEBAUUNG, + property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.WohnBauflaeche, + "besondererArtderBaulNutzung": XP_BesondereArtDerBaulNutzung.Kleinsiedlungsgebiet}), + CivilStyle(civil_id='1.1.2', xtype=BP_BaugebietsTeilFlaeche, civil_properties=CIVIL_PROPERTIES_BEBAUUNG, + property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.WohnBauflaeche, + "besondererArtderBaulNutzung": XP_BesondereArtDerBaulNutzung.ReinesWohngebiet}), + CivilStyle(civil_id='1.1.3', xtype=BP_BaugebietsTeilFlaeche, civil_properties=CIVIL_PROPERTIES_BEBAUUNG, + property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.WohnBauflaeche, + "besondererArtderBaulNutzung": XP_BesondereArtDerBaulNutzung.AllgWohngebiet}), + CivilStyle(civil_id='1.1.4', xtype=BP_BaugebietsTeilFlaeche, civil_properties=CIVIL_PROPERTIES_BEBAUUNG, + property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.WohnBauflaeche, + "besondererArtderBaulNutzung": XP_BesondereArtDerBaulNutzung.BesonderesWohngebiet}), + + CivilStyle(civil_id='1.2 Gemischte Bauflächen', xtype=BP_BaugebietsTeilFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.GemischteBauflaeche}), + CivilStyle(civil_id='1.2.1', xtype=BP_BaugebietsTeilFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.GemischteBauflaeche, + "besondererArtderBaulNutzung": XP_BesondereArtDerBaulNutzung.Dorfgebiet}), + CivilStyle(civil_id='1.2.2', xtype=BP_BaugebietsTeilFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.GemischteBauflaeche, + "besondererArtderBaulNutzung": XP_BesondereArtDerBaulNutzung.Mischgebiet}), + CivilStyle(civil_id='1.2.3', xtype=BP_BaugebietsTeilFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.GemischteBauflaeche, + "besondererArtderBaulNutzung": XP_BesondereArtDerBaulNutzung.Kerngebiet}), + + CivilStyle(civil_id='1.3 Gewerbliche Bauflächen', xtype=BP_BaugebietsTeilFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.GewerblicheBauflaeche}), + CivilStyle(civil_id='1.3.1', xtype=BP_BaugebietsTeilFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.GewerblicheBauflaeche, + "besondererArtderBaulNutzung": XP_BesondereArtDerBaulNutzung.Gewerbegebiet}), + CivilStyle(civil_id='1.3.2', xtype=BP_BaugebietsTeilFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.GewerblicheBauflaeche, + "besondererArtderBaulNutzung": XP_BesondereArtDerBaulNutzung.Industriegebiet}), + + CivilStyle(civil_id='1.4 Sonderbauflächen', xtype=BP_BaugebietsTeilFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.SonderBauflaeche}), + CivilStyle(civil_id='1.4.1', xtype=BP_BaugebietsTeilFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.SonderBauflaeche, + "besondererArtderBaulNutzung": XP_BesondereArtDerBaulNutzung.SondergebietErholung}), + CivilStyle(civil_id='1.4.2', xtype=BP_BaugebietsTeilFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"allgArtDerBaulNutzung": XP_AllgArtDerBaulNutzung.SonderBauflaeche, + "besondererArtderBaulNutzung": XP_BesondereArtDerBaulNutzung.SondergebietSonst}), + + # Gemeinbedarf + CivilStyle(civil_id='4.1.1', xtype=BP_GemeinbedarfsFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGemeinbedarf.OeffentlicheVerwaltung}), + CivilStyle(civil_id='4.1.2', xtype=BP_GemeinbedarfsFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGemeinbedarf.Schule}), + CivilStyle(civil_id='4.1.3', xtype=BP_GemeinbedarfsFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGemeinbedarf.Kirche}), + CivilStyle(civil_id='4.1.4', xtype=BP_GemeinbedarfsFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGemeinbedarf.Sozial}), + CivilStyle(civil_id='4.1.5', xtype=BP_GemeinbedarfsFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGemeinbedarf.Gesundheit}), + CivilStyle(civil_id='4.1.6', xtype=BP_GemeinbedarfsFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGemeinbedarf.Kultur}), + CivilStyle(civil_id='4.1.7', xtype=BP_GemeinbedarfsFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGemeinbedarf.Sport}), + CivilStyle(civil_id='4.1.8', xtype=BP_GemeinbedarfsFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGemeinbedarf.Post}), + CivilStyle(civil_id='4.1.9', xtype=BP_GemeinbedarfsFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGemeinbedarf.Schutzbauwerk}), + CivilStyle(civil_id='4.1.10', xtype=BP_GemeinbedarfsFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGemeinbedarf.Feuerwehr}), + + # Spiel- und Sportanlagen + CivilStyle(civil_id='4.2.1', xtype=BP_SpielSportanlagenFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungSpielSportanlage.Sportanlage}), + CivilStyle(civil_id='4.2.2', xtype=BP_GemeinbedarfsFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungSpielSportanlage.Spielanlage}), + + # Straßenverkehr + CivilStyle(civil_id='6.1', xtype=BP_StrassenVerkehrsFlaeche, property_table=PARCEL_TABLE), + CivilStyle(civil_id='6.3.1', xtype=BP_VerkehrsflaecheBesondererZweckbestimmung, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": BP_ZweckbestimmungStrassenverkehr.Parkierungsflaeche}), + CivilStyle(civil_id='6.3.2', xtype=BP_VerkehrsflaecheBesondererZweckbestimmung, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": BP_ZweckbestimmungStrassenverkehr.Fussgaengerbereich}), + CivilStyle(civil_id='6.3.3', xtype=BP_VerkehrsflaecheBesondererZweckbestimmung, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": BP_ZweckbestimmungStrassenverkehr.VerkehrsberuhigterBereich}), + + # Versorgung / Entsorgung + CivilStyle(civil_id='7.1', xtype=BP_VerEntsorgung, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungVerEntsorgung.Elektrizitaet}), + CivilStyle(civil_id='7.2', xtype=BP_VerEntsorgung, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungVerEntsorgung.Gas}), + CivilStyle(civil_id='7.3', xtype=BP_VerEntsorgung, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungVerEntsorgung.Fernwaermeleitung}), + CivilStyle(civil_id='7.4', xtype=BP_VerEntsorgung, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungVerEntsorgung.Wasser}), + CivilStyle(civil_id='7.5', xtype=BP_VerEntsorgung, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungVerEntsorgung.Abwasser}), + CivilStyle(civil_id='7.6', xtype=BP_VerEntsorgung, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungVerEntsorgung.Abfallentsorgung}), + CivilStyle(civil_id='7.7', xtype=BP_VerEntsorgung, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungVerEntsorgung.Ablagerung}), + + # Gruenflächen + CivilStyle(civil_id='9.1', xtype=BP_GruenFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGruen.Sonstiges}), + CivilStyle(civil_id='9.2', xtype=BP_GruenFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGruen.Parkanlage}), + CivilStyle(civil_id='9.3', xtype=BP_GruenFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGruen.Dauerkleingarten}), + CivilStyle(civil_id='9.4', xtype=BP_GruenFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGruen.Sportplatz}), + CivilStyle(civil_id='9.5', xtype=BP_GruenFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGruen.Spielplatz}), + CivilStyle(civil_id='9.6', xtype=BP_GruenFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGruen.Zeltplatz}), + CivilStyle(civil_id='9.7', xtype=BP_GruenFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGruen.Badeplatz}), + CivilStyle(civil_id='9.8', xtype=BP_GruenFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGruen.Friedhof}), + + # Wasserflächen + CivilStyle(civil_id='10.1.1', xtype=BP_GewaesserFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGewaesser.Wasserflaeche}), + CivilStyle(civil_id='10.1.2', xtype=BP_GewaesserFlaeche, + civil_properties=CIVIL_PROPERTIES_BEBAUUNG, property_table=PARCEL_TABLE, + attributes={"zweckbestimmung": XP_ZweckbestimmungGewaesser.Hafen}), + + # Schutz, Pflege, Entwicklungsflächen + CivilStyle(civil_id='13.3', xtype=BP_SchutzPflegeEntwicklungsFlaeche, property_table=PARCEL_TABLE), +] + +CIVIL_STYLE_LINES = [ + CivilStyle(civil_id='3.4', xtype=BP_BauLinie, property_table=LINE_TABLE), + CivilStyle(civil_id='3.5', xtype=BP_BauGrenze, property_table=LINE_TABLE), + CivilStyle(civil_id='6.2', xtype=BP_StrassenbegrenzungsLinie, property_table=LINE_TABLE), +] + +CIVIL_STYLE_POINTS = [ + CivilStyle(civil_id='13.2.1.2', xtype=BP_AnpflanzungBindungErhaltung, property_table=POINT_TABLE, + attributes={"massnahme": XP_ABEMassnahmenTypen.Anpflanzung, + "gegenstand": XP_AnpflanzungBindungErhaltungsGegenstand.Baeume}) +] diff --git a/src/SAGisXPlanung/processing/provider.py b/src/SAGisXPlanung/processing/provider.py new file mode 100644 index 0000000..016757c --- /dev/null +++ b/src/SAGisXPlanung/processing/provider.py @@ -0,0 +1,41 @@ +import os + +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsProcessingProvider + +from .export_all import ExportAllAlgorithm +from .. import BASE_DIR + + +class SAGisProvider(QgsProcessingProvider): + + def loadAlgorithms(self, *args, **kwargs): + self.addAlgorithm(ExportAllAlgorithm()) + + try: + from .import_civil import ImportCivil3DAlgorithm + self.addAlgorithm(ImportCivil3DAlgorithm()) + except ImportError as e: + pass + + def id(self, *args, **kwargs): + """The ID of your plugin, used for identifying the provider. + + This string should be a unique, short, character only string, + eg "qgis" or "gdal". This string should not be localised. + """ + return 'sagis-xplanung' + + def name(self, *args, **kwargs): + """The human friendly name of your plugin in Processing. + + This string should be as short as possible (e.g. "Lastools", not + "Lastools version 1.0.1 64-bit") and localised. + """ + return 'SAGis XPlanung' + + def icon(self): + """Should return a QIcon which is used for your provider inside + the Processing toolbox. + """ + return QIcon(os.path.join(BASE_DIR, 'gui/resources/sagis_icon.png')) \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/BP_BaugebietsTeilFlaeche.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/BP_BaugebietsTeilFlaeche.svg new file mode 100644 index 0000000..9de2be1 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/BP_BaugebietsTeilFlaeche.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Geschlossene_Bauweise.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Geschlossene_Bauweise.svg new file mode 100644 index 0000000..39cc6fc --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Geschlossene_Bauweise.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Nur_Doppelhaeuser_zulaessig.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Nur_Doppelhaeuser_zulaessig.svg new file mode 100644 index 0000000..e2f9ebb --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Nur_Doppelhaeuser_zulaessig.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Nur_Einzel-_und_Doppelhaeuser_zulaessig.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Nur_Einzel-_und_Doppelhaeuser_zulaessig.svg new file mode 100644 index 0000000..991b4e3 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Nur_Einzel-_und_Doppelhaeuser_zulaessig.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Nur_Einzelhaeuser_zulaessig.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Nur_Einzelhaeuser_zulaessig.svg new file mode 100644 index 0000000..f939daa --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Nur_Einzelhaeuser_zulaessig.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Nur_Hausgruppen_zulaessig.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Nur_Hausgruppen_zulaessig.svg new file mode 100644 index 0000000..4ab7345 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Nur_Hausgruppen_zulaessig.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Offene_Bauweise.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Offene_Bauweise.svg new file mode 100644 index 0000000..532f0cb --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Bauweise_Offene_Bauweise.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_1.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_1.svg new file mode 100644 index 0000000..0f7e55d --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_1.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_10.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_10.svg new file mode 100644 index 0000000..cb21500 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_10.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_11.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_11.svg new file mode 100644 index 0000000..0d49db1 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_11.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_12.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_12.svg new file mode 100644 index 0000000..9596c2c --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_12.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_13.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_13.svg new file mode 100644 index 0000000..e930457 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_13.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_14.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_14.svg new file mode 100644 index 0000000..07b8819 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_14.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_15.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_15.svg new file mode 100644 index 0000000..e9d7734 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_15.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_16.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_16.svg new file mode 100644 index 0000000..1659a61 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_16.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_17.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_17.svg new file mode 100644 index 0000000..2b2eb48 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_17.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_18.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_18.svg new file mode 100644 index 0000000..8fa332c --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_18.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_19.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_19.svg new file mode 100644 index 0000000..11d8a79 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_19.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_2.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_2.svg new file mode 100644 index 0000000..47f98b4 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_2.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_20.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_20.svg new file mode 100644 index 0000000..128c8fc --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_20.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_21.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_21.svg new file mode 100644 index 0000000..f83d561 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_21.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_22.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_22.svg new file mode 100644 index 0000000..5c97ac0 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_22.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_23.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_23.svg new file mode 100644 index 0000000..a86a629 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_23.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_24.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_24.svg new file mode 100644 index 0000000..19159a0 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_24.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_25.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_25.svg new file mode 100644 index 0000000..90e18ca --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_25.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_26.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_26.svg new file mode 100644 index 0000000..dd41cf6 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_26.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_27.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_27.svg new file mode 100644 index 0000000..7916370 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_27.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_28.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_28.svg new file mode 100644 index 0000000..b537f62 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_28.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_29.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_29.svg new file mode 100644 index 0000000..4f02597 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_29.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_3.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_3.svg new file mode 100644 index 0000000..08df063 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_3.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_30.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_30.svg new file mode 100644 index 0000000..a4ecd8b --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_30.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_4.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_4.svg new file mode 100644 index 0000000..581ece9 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_4.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_5.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_5.svg new file mode 100644 index 0000000..2fbccb8 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_5.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_6.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_6.svg new file mode 100644 index 0000000..2be4a2d --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_6.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_7.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_7.svg new file mode 100644 index 0000000..e6a6a3a --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_7.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_8.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_8.svg new file mode 100644 index 0000000..dbaf144 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_8.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_9.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_9.svg new file mode 100644 index 0000000..44f88d8 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Beschraenkung_der_Zahl_der_Wohnungen_SW_Kreis_9.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Dorfgebiet.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Dorfgebiet.svg new file mode 100644 index 0000000..5d01f84 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Dorfgebiet.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.1.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.1.svg new file mode 100644 index 0000000..abffc6f --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.1.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.2.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.2.svg new file mode 100644 index 0000000..1665f96 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.2.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.3.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.3.svg new file mode 100644 index 0000000..7a4b3f9 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.3.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.4.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.4.svg new file mode 100644 index 0000000..66633db --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.4.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.5.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.5.svg new file mode 100644 index 0000000..2adf059 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.5.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.6.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.6.svg new file mode 100644 index 0000000..77885ad --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.6.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.7.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.7.svg new file mode 100644 index 0000000..0d3f048 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.7.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.8.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.8.svg new file mode 100644 index 0000000..c2314d7 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.8.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.9.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.9.svg new file mode 100644 index 0000000..e542b4b --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_0.9.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_1.0.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_1.0.svg new file mode 100644 index 0000000..a8a6f74 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Geschossflaechenzahl_Kreis_SW_1.0.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Gewerbegebiet.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Gewerbegebiet.svg new file mode 100644 index 0000000..7fd98fc --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Gewerbegebiet.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/GewerblicheBauflaeche.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/GewerblicheBauflaeche.svg new file mode 100644 index 0000000..548a421 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/GewerblicheBauflaeche.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Firsthoehe_SW.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Firsthoehe_SW.svg new file mode 100644 index 0000000..a22ab88 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Firsthoehe_SW.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Firsthoehe_zwingend_Kreis_SW.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Firsthoehe_zwingend_Kreis_SW.svg new file mode 100644 index 0000000..169b4dc --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Firsthoehe_zwingend_Kreis_SW.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Oberkante_SW.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Oberkante_SW.svg new file mode 100644 index 0000000..326bd2b --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Oberkante_SW.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Oberkante_zwingend_Kreis_SW_1.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Oberkante_zwingend_Kreis_SW_1.svg new file mode 100644 index 0000000..700f09c --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Oberkante_zwingend_Kreis_SW_1.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Traufhoehe_SW.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Traufhoehe_SW.svg new file mode 100644 index 0000000..4454649 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Traufhoehe_SW.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Traufhoehe_zwingend_Kreis_SW.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Traufhoehe_zwingend_Kreis_SW.svg new file mode 100644 index 0000000..cea7f0f --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Hoehe_baulicher_Anlagen_als_Hoechstmass_Traufhoehe_zwingend_Kreis_SW.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Kerngebiet.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Kerngebiet.svg new file mode 100644 index 0000000..f46072e --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Kerngebiet.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Baumasse_SW_Text.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Baumasse_SW_Text.svg new file mode 100644 index 0000000..efa6756 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Baumasse_SW_Text.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Baumassenzahl_SW_Text.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Baumassenzahl_SW_Text.svg new file mode 100644 index 0000000..eede14b --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Baumassenzahl_SW_Text.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Geschossflaechenzahl_SW_Text.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Geschossflaechenzahl_SW_Text.svg new file mode 100644 index 0000000..a66449e --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Geschossflaechenzahl_SW_Text.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Geschossflaechenzahl_bis_SW_Text.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Geschossflaechenzahl_bis_SW_Text.svg new file mode 100644 index 0000000..3f61f26 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Geschossflaechenzahl_bis_SW_Text.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Grundflaeche_SW_Text.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Grundflaeche_SW_Text.svg new file mode 100644 index 0000000..55f7c9d --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Grundflaeche_SW_Text.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Grundflaechenzahl_SW_Text.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Grundflaechenzahl_SW_Text.svg new file mode 100644 index 0000000..a636982 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Grundflaechenzahl_SW_Text.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_1.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_1.svg new file mode 100644 index 0000000..2fd1604 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_10.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_10.svg new file mode 100644 index 0000000..f88d892 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_10.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_2.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_2.svg new file mode 100644 index 0000000..ac301cd --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_2.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_3.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_3.svg new file mode 100644 index 0000000..2c8a859 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_3.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_4.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_4.svg new file mode 100644 index 0000000..89e1053 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_4.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_5.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_5.svg new file mode 100644 index 0000000..2cfcabe --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_5.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_6.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_6.svg new file mode 100644 index 0000000..0aa1b4a --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_6.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_7.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_7.svg new file mode 100644 index 0000000..01ade41 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_7.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_8.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_8.svg new file mode 100644 index 0000000..dc2ab9f --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_8.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_9.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_9.svg new file mode 100644 index 0000000..0da7f9d --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_SW_9.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_1.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_1.svg new file mode 100644 index 0000000..df4102f --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_1.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_10.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_10.svg new file mode 100644 index 0000000..c05b568 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_10.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_2.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_2.svg new file mode 100644 index 0000000..324a66d --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_2.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_3.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_3.svg new file mode 100644 index 0000000..aad7ede --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_3.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_4.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_4.svg new file mode 100644 index 0000000..03405b5 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_4.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_5.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_5.svg new file mode 100644 index 0000000..834bf64 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_5.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_6.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_6.svg new file mode 100644 index 0000000..1cb4714 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_6.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_7.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_7.svg new file mode 100644 index 0000000..2b65709 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_7.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_8.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_8.svg new file mode 100644 index 0000000..930f972 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_8.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_9.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_9.svg new file mode 100644 index 0000000..a55bc7d --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mass_der_baulichen_Nutzung_Zahl_der_Vollgeschosse_zwingend_SW_9.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Mischgebiet.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Mischgebiet.svg new file mode 100644 index 0000000..20952b4 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Mischgebiet.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Sondergebiet_Camping.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Sondergebiet_Camping.svg new file mode 100644 index 0000000..639625f --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Sondergebiet_Camping.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Sondergebiet_Ferienhaus.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Sondergebiet_Ferienhaus.svg new file mode 100644 index 0000000..f3105d4 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Sondergebiet_Ferienhaus.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Sondergebiet_Woch.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Sondergebiet_Woch.svg new file mode 100644 index 0000000..fa10765 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Sondergebiet_Woch.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche.svg new file mode 100644 index 0000000..eb21d64 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche.svg @@ -0,0 +1,235 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +W diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche_1.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche_1.svg new file mode 100644 index 0000000..4af7138 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche_1.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + W1 + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche_2.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche_2.svg new file mode 100644 index 0000000..81b552e --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche_2.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + W2 + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche_3.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche_3.svg new file mode 100644 index 0000000..910a9b9 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche_3.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + W3 + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche_4.svg b/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche_4.svg new file mode 100644 index 0000000..6ed8306 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Bebauung/Wohnbauflaeche_4.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + W4 + + + + + + + + \ No newline at end of file diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Allgemeinbildende_Schule.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Allgemeinbildende_Schule.svg new file mode 100644 index 0000000..cf25c4c --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Allgemeinbildende_Schule.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Alteneinrichtung.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Alteneinrichtung.svg new file mode 100644 index 0000000..9f02123 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Alteneinrichtung.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Anlage_Spielanlage.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Anlage_Spielanlage.svg new file mode 100644 index 0000000..a63f502 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Anlage_Spielanlage.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Anlage_Sportanlage.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Anlage_Sportanlage.svg new file mode 100644 index 0000000..e0552fc --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Anlage_Sportanlage.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Berufsbildende_Schule.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Berufsbildende_Schule.svg new file mode 100644 index 0000000..086edd8 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Berufsbildende_Schule.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Bildung_Forschung.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Bildung_Forschung.svg new file mode 100644 index 0000000..8038135 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Bildung_Forschung.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Einrichtung_Kultur.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Einrichtung_Kultur.svg new file mode 100644 index 0000000..8fd854d --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Einrichtung_Kultur.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Einrichtung_Soziales.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Einrichtung_Soziales.svg new file mode 100644 index 0000000..c2356ef --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Einrichtung_Soziales.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Feuerwehr.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Feuerwehr.svg new file mode 100644 index 0000000..27673fd --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Feuerwehr.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Gemeindebehoerde.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Gemeindebehoerde.svg new file mode 100644 index 0000000..97383ca --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Gemeindebehoerde.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Gericht.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Gericht.svg new file mode 100644 index 0000000..af829d1 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Gericht.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Hallenbad.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Hallenbad.svg new file mode 100644 index 0000000..41b0e83 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Hallenbad.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Hochschule.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Hochschule.svg new file mode 100644 index 0000000..0af1b53 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Hochschule.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Jugendfreizeitheim.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Jugendfreizeitheim.svg new file mode 100644 index 0000000..8653a10 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Jugendfreizeitheim.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Jugendheim.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Jugendheim.svg new file mode 100644 index 0000000..5c7df1f --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Jugendheim.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Justizvollzug.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Justizvollzug.svg new file mode 100644 index 0000000..b8bdbef --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Justizvollzug.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Kindergarten.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Kindergarten.svg new file mode 100644 index 0000000..4a9eae5 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Kindergarten.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Kirche.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Kirche.svg new file mode 100644 index 0000000..7acad6a --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Kirche.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Kirchliche_Einrichtung.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Kirchliche_Einrichtung.svg new file mode 100644 index 0000000..d3648e3 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Kirchliche_Einrichtung.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Krankenhaus.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Krankenhaus.svg new file mode 100644 index 0000000..4b3933b --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Krankenhaus.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Kreisbehoerde.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Kreisbehoerde.svg new file mode 100644 index 0000000..8eda20a --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Kreisbehoerde.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Landesbehoerde.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Landesbehoerde.svg new file mode 100644 index 0000000..ed93a41 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Landesbehoerde.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Museum.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Museum.svg new file mode 100644 index 0000000..8b65f10 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Museum.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Oeffentliche_Verwaltung.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Oeffentliche_Verwaltung.svg new file mode 100644 index 0000000..e88a39f --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Oeffentliche_Verwaltung.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Parkplatz.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Parkplatz.svg new file mode 100644 index 0000000..ac22202 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Parkplatz.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Polizei.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Polizei.svg new file mode 100644 index 0000000..8863c51 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Polizei.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Post.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Post.svg new file mode 100644 index 0000000..0b8295a --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Post.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Schule.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Schule.svg new file mode 100644 index 0000000..ec3d5c0 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Schule.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Schutzbauwerk.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Schutzbauwerk.svg new file mode 100644 index 0000000..05983eb --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Schutzbauwerk.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Theater.svg b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Theater.svg new file mode 100644 index 0000000..9fef281 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Gemeinbedarf_Spiel_und_Sportanlagen/Theater.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Badeplatz.svg b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Badeplatz.svg new file mode 100644 index 0000000..7aa53dc --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Badeplatz.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Dauerkleing\303\244rten.svg" "b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Dauerkleing\303\244rten.svg" new file mode 100644 index 0000000..ef54adc --- /dev/null +++ "b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Dauerkleing\303\244rten.svg" @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Erholungswald_Kreis_SW.svg b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Erholungswald_Kreis_SW.svg new file mode 100644 index 0000000..066668d --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Erholungswald_Kreis_SW.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Friedhof.svg b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Friedhof.svg new file mode 100644 index 0000000..94fad1f --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Friedhof.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Parkanlage.svg b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Parkanlage.svg new file mode 100644 index 0000000..c5e2817 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Parkanlage.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Spielplatz.svg b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Spielplatz.svg new file mode 100644 index 0000000..431517d --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Spielplatz.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Sportplatz.svg b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Sportplatz.svg new file mode 100644 index 0000000..90c97d7 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Sportplatz.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Zeltplatz.svg b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Zeltplatz.svg new file mode 100644 index 0000000..9cc6053 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Landwirtschaft_Wald_und_Gruenflaechen/Zeltplatz.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Baum.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Baum.svg new file mode 100644 index 0000000..757c1a9 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Baum.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Baum_SW.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Baum_SW.svg new file mode 100644 index 0000000..893fc5e --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Baum_SW.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Sonstiges.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Sonstiges.svg new file mode 100644 index 0000000..b4d4412 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Sonstiges.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Sonstiges_SW.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Sonstiges_SW.svg new file mode 100644 index 0000000..2e2adec --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Sonstiges_SW.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Straeucher.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Straeucher.svg new file mode 100644 index 0000000..58425d5 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Straeucher.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Straeucher_SW.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Straeucher_SW.svg new file mode 100644 index 0000000..bbe2a66 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Anpflanzen_Straeucher_SW.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Baum.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Baum.svg new file mode 100644 index 0000000..4820621 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Baum.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Baum_SW.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Baum_SW.svg new file mode 100644 index 0000000..34519d4 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Baum_SW.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Sonstiges.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Sonstiges.svg new file mode 100644 index 0000000..96d44c6 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Sonstiges.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Sonstiges_SW.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Sonstiges_SW.svg new file mode 100644 index 0000000..8c11599 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Sonstiges_SW.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Straeucher.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Straeucher.svg new file mode 100644 index 0000000..5149b0b --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Straeucher.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Straeucher_SW.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Straeucher_SW.svg new file mode 100644 index 0000000..1518e35 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Erhaltung_Straeucher_SW.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Geschuetzter_Landschaftsbestandteil_SW.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Geschuetzter_Landschaftsbestandteil_SW.svg new file mode 100644 index 0000000..2954ce0 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Geschuetzter_Landschaftsbestandteil_SW.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Landschaftsschutzgebiet_SW.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Landschaftsschutzgebiet_SW.svg new file mode 100644 index 0000000..e28b21b --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Landschaftsschutzgebiet_SW.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Nationalpark_SW.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Nationalpark_SW.svg new file mode 100644 index 0000000..d0e9161 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Nationalpark_SW.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Naturdenkmal_SW.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Naturdenkmal_SW.svg new file mode 100644 index 0000000..2ab4277 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Naturdenkmal_SW.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Naturpark_SW.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Naturpark_SW.svg new file mode 100644 index 0000000..cc3223a --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Naturpark_SW.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Naturschutzgebiet_SW.svg b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Naturschutzgebiet_SW.svg new file mode 100644 index 0000000..786d3cf --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Naturschutz_Landschaftsbild_Naturhaushalt/Schutzgebiet_Naturschutzgebiet_SW.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Abfall.svg b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Abfall.svg new file mode 100644 index 0000000..2dd9450 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Abfall.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Ablagerung.svg b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Ablagerung.svg new file mode 100644 index 0000000..9032362 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Ablagerung.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Abwasser.svg b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Abwasser.svg new file mode 100644 index 0000000..24c7063 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Abwasser.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Elektrizitaet.svg b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Elektrizitaet.svg new file mode 100644 index 0000000..7484f1b --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Elektrizitaet.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Erneuerbare_Energien.svg b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Erneuerbare_Energien.svg new file mode 100644 index 0000000..5207c1c --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Erneuerbare_Energien.svg @@ -0,0 +1,10 @@ + + + + + + + + EE + + diff --git a/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Fernwaerme.svg b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Fernwaerme.svg new file mode 100644 index 0000000..e640f35 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Fernwaerme.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Gas.svg b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Gas.svg new file mode 100644 index 0000000..cfc6a52 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Gas.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Kraft_Waerme_Kopplung.svg b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Kraft_Waerme_Kopplung.svg new file mode 100644 index 0000000..ecc9526 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Kraft_Waerme_Kopplung.svg @@ -0,0 +1,10 @@ + + + + + + + + KWK + + diff --git a/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Wasser.svg b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Wasser.svg new file mode 100644 index 0000000..16f2119 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Ver_und_Entsorgung/Wasser.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Verkehr/Flughafen_Kreis_SW.svg b/src/SAGisXPlanung/symbole/BP_Verkehr/Flughafen_Kreis_SW.svg new file mode 100644 index 0000000..bc8c188 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Verkehr/Flughafen_Kreis_SW.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Verkehr/Fussgaengerbereich.svg b/src/SAGisXPlanung/symbole/BP_Verkehr/Fussgaengerbereich.svg new file mode 100644 index 0000000..c416dda --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Verkehr/Fussgaengerbereich.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Verkehr/Hubschrauberlandeplatz_Kreis_SW.svg b/src/SAGisXPlanung/symbole/BP_Verkehr/Hubschrauberlandeplatz_Kreis_SW.svg new file mode 100644 index 0000000..82997ba --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Verkehr/Hubschrauberlandeplatz_Kreis_SW.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Verkehr/Landeplatz_Kreis_SW.svg b/src/SAGisXPlanung/symbole/BP_Verkehr/Landeplatz_Kreis_SW.svg new file mode 100644 index 0000000..a4dcd2d --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Verkehr/Landeplatz_Kreis_SW.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Verkehr/Parkierungsflaeche.svg b/src/SAGisXPlanung/symbole/BP_Verkehr/Parkierungsflaeche.svg new file mode 100644 index 0000000..c9cf624 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Verkehr/Parkierungsflaeche.svg @@ -0,0 +1,12 @@ + + + + + + + + + P + + + diff --git a/src/SAGisXPlanung/symbole/BP_Verkehr/Segelflugflaeche_Kreis_SW.svg b/src/SAGisXPlanung/symbole/BP_Verkehr/Segelflugflaeche_Kreis_SW.svg new file mode 100644 index 0000000..cd9db26 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Verkehr/Segelflugflaeche_Kreis_SW.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Verkehr/VerkehrsberuhigterBereich.svg b/src/SAGisXPlanung/symbole/BP_Verkehr/VerkehrsberuhigterBereich.svg new file mode 100644 index 0000000..3b89c50 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Verkehr/VerkehrsberuhigterBereich.svg @@ -0,0 +1,12 @@ + + + + + + + + + V + + + diff --git a/src/SAGisXPlanung/symbole/BP_Wasser/Hafen.svg b/src/SAGisXPlanung/symbole/BP_Wasser/Hafen.svg new file mode 100644 index 0000000..247709d --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Wasser/Hafen.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Wasser/Hochwasserrueckhaltebecken.svg b/src/SAGisXPlanung/symbole/BP_Wasser/Hochwasserrueckhaltebecken.svg new file mode 100644 index 0000000..08e9cfa --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Wasser/Hochwasserrueckhaltebecken.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Wasser/Schutzgebiet_fuer_Grund-_und_Quellwassergewinnung.svg b/src/SAGisXPlanung/symbole/BP_Wasser/Schutzgebiet_fuer_Grund-_und_Quellwassergewinnung.svg new file mode 100644 index 0000000..3643227 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Wasser/Schutzgebiet_fuer_Grund-_und_Quellwassergewinnung.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Wasser/Schutzgebiet_fuer_Oberflaechenwasser.svg b/src/SAGisXPlanung/symbole/BP_Wasser/Schutzgebiet_fuer_Oberflaechenwasser.svg new file mode 100644 index 0000000..7be75ba --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Wasser/Schutzgebiet_fuer_Oberflaechenwasser.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/BP_Wasser/Ueberschwemmungsgebiet.svg b/src/SAGisXPlanung/symbole/BP_Wasser/Ueberschwemmungsgebiet.svg new file mode 100644 index 0000000..15146d1 --- /dev/null +++ b/src/SAGisXPlanung/symbole/BP_Wasser/Ueberschwemmungsgebiet.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/SO_SonstigeGebiete/Denkmalschutz_Einzelanlagen_SW.svg b/src/SAGisXPlanung/symbole/SO_SonstigeGebiete/Denkmalschutz_Einzelanlagen_SW.svg new file mode 100644 index 0000000..3b4ea30 --- /dev/null +++ b/src/SAGisXPlanung/symbole/SO_SonstigeGebiete/Denkmalschutz_Einzelanlagen_SW.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/Sonstiges/Hoehenlage_SW.svg b/src/SAGisXPlanung/symbole/Sonstiges/Hoehenlage_SW.svg new file mode 100644 index 0000000..20b88cc --- /dev/null +++ b/src/SAGisXPlanung/symbole/Sonstiges/Hoehenlage_SW.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/symbole/Sonstiges/Kennzeichnung_derLage_ohne_Flaechendarstellung.svg b/src/SAGisXPlanung/symbole/Sonstiges/Kennzeichnung_derLage_ohne_Flaechendarstellung.svg new file mode 100644 index 0000000..d672745 --- /dev/null +++ b/src/SAGisXPlanung/symbole/Sonstiges/Kennzeichnung_derLage_ohne_Flaechendarstellung.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/ui/XPlanung_create.ui b/src/SAGisXPlanung/ui/XPlanung_create.ui new file mode 100644 index 0000000..81ca9a6 --- /dev/null +++ b/src/SAGisXPlanung/ui/XPlanung_create.ui @@ -0,0 +1,26 @@ + + + windowCreate + + + + 0 + 0 + 500 + 600 + + + + + 0 + 0 + + + + Neuen Plan anlegen + + + + + + diff --git a/src/SAGisXPlanung/ui/XPlanung_dialog_base.ui b/src/SAGisXPlanung/ui/XPlanung_dialog_base.ui new file mode 100644 index 0000000..875627e --- /dev/null +++ b/src/SAGisXPlanung/ui/XPlanung_dialog_base.ui @@ -0,0 +1,461 @@ + + + XPlanungDialogBase + + + + 0 + 0 + 395 + 407 + + + + XPlanung + + + + + 0 + 0 + + + + + + + Werkzeuge + + + + + + + 0 + 0 + + + + Planinhalte konfigurieren / abfragen + + + ... + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Neuen Plan erfassen + + + + + + + + + + + 0 + 0 + + + + Bebauungsplan + + + true + + + + + + + + 0 + 0 + + + + Flächennutzungsplan + + + + + + + + + + + + 0 + 0 + + + + Raumordnungsplan + + + + + + + + 0 + 0 + + + + Landschaftsplan + + + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + padding-left:5px;padding-right:5px; + + + Planwerk erfassen + + + + + + + + + + + + + 0 + 0 + + + + XPlanGML Import + + + + + + + + + 0 + 0 + + + + Pfad + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + 0 + 0 + + + + PointingHandCursor + + + Import + + + + + + + + + 45 + + + QLayout::SetNoConstraint + + + + + + 0 + 0 + + + + true + + + false + + + false + + + GML (*.gml);;ZIP (*.zip) + + + + + + + + + + + + + 0 + 0 + + + + font-weight: 400; +color: #374151; + + + TextLabel + + + + + + + font-weight: 400; +color: #374151; + + + Objekte gelesen + + + + + + + + + + + + + + + + + + + 5 + + + + + + 0 + 0 + + + + margin-right:10px; + + + Planwerk + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + Details anzeigen + + + ... + + + + + + + + + Planwerk exportieren + + + + + + 15 + + + + + + 0 + 0 + + + + PointingHandCursor + + + Export + + + + + + + + 0 + 0 + + + + XPlanGML + + + true + + + + + + + + 0 + 0 + + + + ZIP-Archiv + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + QgsDockWidget + QDockWidget +
    qgsdockwidget.h
    + 1 +
    + + QgsFileWidget + QWidget +
    qgsfilewidget.h
    +
    + + QPlanComboBox + QComboBox +
    SAGisXPlanung/gui/widgets/QPlanComboBox.h
    +
    +
    + + +
    diff --git a/src/SAGisXPlanung/ui/XPlanung_edit_attribute.ui b/src/SAGisXPlanung/ui/XPlanung_edit_attribute.ui new file mode 100644 index 0000000..b3a6d9e --- /dev/null +++ b/src/SAGisXPlanung/ui/XPlanung_edit_attribute.ui @@ -0,0 +1,76 @@ + + + XPEditAttributeDialog + + + + 0 + 0 + 400 + 120 + + + + Attribut bearbeiten + + + + + + Attribut + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Discard|QDialogButtonBox::Save + + + + + + + + + buttonBox + accepted() + XPEditAttributeDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + XPEditAttributeDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/SAGisXPlanung/ui/XPlanung_plan_details.ui b/src/SAGisXPlanung/ui/XPlanung_plan_details.ui new file mode 100644 index 0000000..b0b4597 --- /dev/null +++ b/src/SAGisXPlanung/ui/XPlanung_plan_details.ui @@ -0,0 +1,634 @@ + + + PlanDetailsDialog + + + + 0 + 0 + 419 + 644 + + + + + 0 + 0 + + + + Planwerk Details + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + + + + 0 + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + + 10 + 75 + true + true + + + + <template-name> + + + true + + + + + + + 40 + + + + + + 0 + 0 + + + + <html><head/><body><p>&lt;template-type&gt;</p></body></html> + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 0 + + + + Planwerk auf Karte anzeigen + + + ... + + + + + + + ... + + + + + + + + 0 + 0 + + + + Gesamten Plan löschen + + + ... + + + + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + Geometrieprüfung + + + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + 2 + + + + Fläche + + + + + Fehler + + + + + + + + + + + + + 0 + 0 + + + + Flächenschluss + erzwingen + + + + + + + + 0 + 0 + + + + Geometrieprüfung + starten + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 10 + + + + + + + + Geometrieprüfung +abgeschlossen. + + + + + + + 12 Fehler gefunden + + + + + + + Geometrieprüfung und markierte Geometriefehler zurücksetzen. + + + font-weight: bold; font-size: 10px; + + + QFrame::NoFrame + + + <a href="..link">Zurücksetzen</a> + + + + + + + + + + + + + + + 0 + 0 + + + + XPlanung Explorer + + + + + + 0 + + + + + + 0 + 0 + + + + #container { + padding: 0px; + margin:0px; +} + +QToolButton { + background: palette(window); + border:0px; + border-radius: 3px; +} + +QToolButton:hover { + background: #E5E7EB; + border: 1px solid #BFDBFE ; +} + +QToolButton:checked { + background-color:#93C5FD; + border: none; + } + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + PointingHandCursor + + + Nach Objekthierarchie sortieren + + + ... + + + + 20 + 20 + + + + true + + + true + + + sortButtons + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + Alphabetisch sortieren + + + ... + + + + 20 + 20 + + + + true + + + sortButtons + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + Nach Kategorie sortieren + + + ... + + + + 20 + 20 + + + + true + + + sortButtons + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + true + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + false + + + <Zurück + + + + + + + Bearbeiten + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + ... + + + + + + + ... + + + + + + + + 0 + 0 + + + + QDialogButtonBox::Save + + + + + + + + + + + QgsDockWidget + QDockWidget +
    qgsdockwidget.h
    + 1 +
    + + QExplorerView + QTreeView +
    SAGisXPlanung/gui/widgets/QExplorerView.h
    +
    + + QCollapsibleSearch + QLineEdit +
    SAGisXPlanung/gui/widgets/QCollapsibleSearch.h
    +
    + + QParishLabel + QWidget +
    SAGisXPlanung/gui/widgets.h
    + 1 +
    + + QParishEdit + QWidget +
    SAGisXPlanung/gui/widgets.h
    + 1 +
    +
    + + + + + +
    diff --git a/src/SAGisXPlanung/ui/attribute_edit.ui b/src/SAGisXPlanung/ui/attribute_edit.ui new file mode 100644 index 0000000..c85877e --- /dev/null +++ b/src/SAGisXPlanung/ui/attribute_edit.ui @@ -0,0 +1,204 @@ + + + Form + + + + 0 + 0 + 380 + 385 + + + + Form + + + + + + Suchen... + + + true + + + + + + + + 0 + 0 + + + + + + + + Darstellungsoptionen + + + false + + + + + + Größe + + + + + + + + 0 + 0 + + + + Drehwinkel + + + + + + + + + + 0 + 0 + + + + + + + + true + + + + 0 + 0 + + + + + 40 + 40 + + + + 359 + + + Qt::Horizontal + + + true + + + true + + + true + + + false + + + + + + + + + + + 1 + + + 99 + + + 1 + + + 50 + + + true + + + Qt::Horizontal + + + QSlider::NoTicks + + + + + + + + + Klein + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Mittel + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Groß + + + + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/ui/create_annotation.ui b/src/SAGisXPlanung/ui/create_annotation.ui new file mode 100644 index 0000000..fd8abfc --- /dev/null +++ b/src/SAGisXPlanung/ui/create_annotation.ui @@ -0,0 +1,137 @@ + + + Dialog + + + + 0 + 0 + 400 + 300 + + + + Planwerk annotieren + + + + + + 0 + + + + Symbol + + + + + + Beschriftung + + + + + + Beschriftung + + + + + + 50 + + + + + Text + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + + + true + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/SAGisXPlanung/ui/prefilled_object_edit.ui b/src/SAGisXPlanung/ui/prefilled_object_edit.ui new file mode 100644 index 0000000..8868f9b --- /dev/null +++ b/src/SAGisXPlanung/ui/prefilled_object_edit.ui @@ -0,0 +1,125 @@ + + + Dialog + + + + 0 + 0 + 400 + 300 + + + + Allgemeine Daten bearbeiten + + + + + + true + + + 0 + + + + + + + + + Objektklasse + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Maximum + + + + 20 + 40 + + + + + + + + true + + + Gespeicherte Objekte + + + + + + + + PointingHandCursor + + + Neues Objekt + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + true + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + + + + + + + + + + diff --git a/src/SAGisXPlanung/ui/settings.ui b/src/SAGisXPlanung/ui/settings.ui new file mode 100644 index 0000000..14c6556 --- /dev/null +++ b/src/SAGisXPlanung/ui/settings.ui @@ -0,0 +1,502 @@ + + + Dialog + + + + 0 + 0 + 412 + 393 + + + + Einstellungen + + + + + + 2 + + + + true + + + Allgemein + + + Allgemeine Einstellungen + + + + + + + 0 + 0 + + + + Exportoptionen + + + + + + Pfad für externe Referenzen + + + + + + + referenzen/ + + + + + + + Referenzen im Hauptordner speichern + + + + + + + + + + XPlanung - Version + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Formulare + + + Konfigurieren der Formulare zur Eingabe von XPlanung Sachdaten + + + + + + + + + + Datenbank + + + + + + 1 + + + + Verbindung + + + + 0 + + + 9 + + + 0 + + + 9 + + + + + + 0 + 0 + + + + Datenbankverbindung + + + + + + PostGIS Verbindung + + + + + + + + + + Nutzername + + + + + + + + + + Passwort + + + + + + + QLineEdit::Password + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Neue Datenbank anlegen + + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Verbindungsinformationen + + + + + + Name + + + + + + + + + + Host + + + + + + + + + + Port + + + + + + + 5432 + + + + + + + + 0 + 0 + + + + Authentifizierung + + + false + + + + + + Passwort + + + + + + + QLineEdit::Password + + + + + + + postgres + + + + + + + Nutzername + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Erstellen + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + color: #4b5563; + + + + + + + + + + Qt::Horizontal + + + + 40 + 10 + + + + + + + + + + + + + + + + + QFrame::Plain + + + Qt::Horizontal + + + + + + + QLayout::SetDefaultConstraint + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 7 + + + + + + + SAGis XPlanung + + + Qt::AlignCenter + + + + + + + + 7 + + + + <template> + + + + + + + + 7 + + + + - + + + + + + + + 0 + 0 + + + + + 7 + + + + <a href="https://www.nti.biz/de/produkte/sagis-loesungen/">nti.biz/de</a> + + + Qt::AlignCenter + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + diff --git a/src/SAGisXPlanung/utils.py b/src/SAGisXPlanung/utils.py new file mode 100644 index 0000000..ca650c7 --- /dev/null +++ b/src/SAGisXPlanung/utils.py @@ -0,0 +1,256 @@ +import asyncio +import logging +import os +from urllib.parse import urlparse + +from qgis.PyQt.QtWidgets import QMessageBox +from qgis.PyQt import QtGui +from qgis.gui import QgsLayerTreeViewIndicator +from qgis.utils import iface + +from SAGisXPlanung import Session, Base +from SAGisXPlanung.BPlan.BP_Basisobjekte.data_types import BP_VeraenderungssperreDaten +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Plan, BP_Bereich, BP_Objekt +from SAGisXPlanung.BPlan.BP_Bebauung.data_types import BP_Dachgestaltung, BP_KomplexeSondernutzung +from SAGisXPlanung.BPlan.BP_Bebauung.feature_types import BP_BaugebietsTeilFlaeche, BP_BauGrenze, BP_BauLinie, \ + BP_BesondererNutzungszweckFlaeche +from SAGisXPlanung.BPlan.BP_Gemeinbedarf_Spiel_und_Sportanlagen.data_types import BP_KomplexeZweckbestSpielSportanlage, \ + BP_KomplexeZweckbestGemeinbedarf +from SAGisXPlanung.BPlan.BP_Gemeinbedarf_Spiel_und_Sportanlagen.feature_types import BP_GemeinbedarfsFlaeche, \ + BP_SpielSportanlagenFlaeche +from SAGisXPlanung.BPlan.BP_Landwirtschaft_Wald_und_Gruenflaechen.data_types import BP_KomplexeZweckbestGruen, \ + BP_KomplexeZweckbestLandwirtschaft, BP_KomplexeZweckbestWald +from SAGisXPlanung.BPlan.BP_Landwirtschaft_Wald_und_Gruenflaechen.feature_types import BP_GruenFlaeche, \ + BP_LandwirtschaftsFlaeche, BP_WaldFlaeche +from SAGisXPlanung.BPlan.BP_Naturschutz_Landschaftsbild_Naturhaushalt.feature_types import BP_AnpflanzungBindungErhaltung, \ + BP_SchutzPflegeEntwicklungsFlaeche +from SAGisXPlanung.BPlan.BP_Sonstiges.feature_types import BP_FlaecheOhneFestsetzung, BP_Wegerecht, \ + BP_NutzungsartenGrenze +from SAGisXPlanung.BPlan.BP_Ver_und_Entsorgung.data_types import BP_KomplexeZweckbestVerEntsorgung +from SAGisXPlanung.BPlan.BP_Ver_und_Entsorgung.feature_types import BP_VerEntsorgung +from SAGisXPlanung.BPlan.BP_Verkehr.feature_types import BP_StrassenVerkehrsFlaeche, BP_StrassenbegrenzungsLinie, \ + BP_VerkehrsflaecheBesondererZweckbestimmung, BP_BereichOhneEinAusfahrtLinie, BP_EinfahrtPunkt +from SAGisXPlanung.BPlan.BP_Wasser.feature_types import BP_GewaesserFlaeche +from SAGisXPlanung.FPlan.FP_Basisobjekte.feature_types import FP_Plan, FP_Bereich, FP_Objekt +from SAGisXPlanung.FPlan.FP_Bebauung.data_types import FP_KomplexeSondernutzung +from SAGisXPlanung.FPlan.FP_Bebauung.feature_types import FP_BebauungsFlaeche +from SAGisXPlanung.FPlan.FP_Gemeinbedarf.data_types import FP_KomplexeZweckbestGemeinbedarf, \ + FP_KomplexeZweckbestSpielSportanlage +from SAGisXPlanung.FPlan.FP_Gemeinbedarf.feature_types import FP_Gemeinbedarf, FP_SpielSportanlage +from SAGisXPlanung.FPlan.FP_Landwirtschaft_Wald_und_Gruen.data_types import FP_KomplexeZweckbestGruen, \ + FP_KomplexeZweckbestLandwirtschaft, FP_KomplexeZweckbestWald +from SAGisXPlanung.FPlan.FP_Landwirtschaft_Wald_und_Gruen.feature_types import FP_Gruen, FP_Landwirtschaft, FP_WaldFlaeche +from SAGisXPlanung.FPlan.FP_Verkehr.feature_types import FP_Strassenverkehr +from SAGisXPlanung.FPlan.FP_Wasser.feature_types import FP_Gewaesser +from SAGisXPlanung.LPlan.LP_Basisobjekte.feature_types import LP_Plan, LP_Bereich +from SAGisXPlanung.RPlan.RP_Basisobjekte.feature_types import RP_Bereich, RP_Plan +from SAGisXPlanung.SonstigePlanwerke.SO_Basisobjekte import SO_Objekt +from SAGisXPlanung.SonstigePlanwerke.SO_NachrichtlicheUebernahmen import SO_Schienenverkehrsrecht, SO_Denkmalschutzrecht +from SAGisXPlanung.SonstigePlanwerke.SO_NachrichtlicheUebernahmen.data_types import SO_KomplexeZweckbestStrassenverkehr, \ + SO_KomplexeFestlegungGewaesser +from SAGisXPlanung.SonstigePlanwerke.SO_NachrichtlicheUebernahmen.feature_types import SO_Strassenverkehr, SO_Gewaesser, \ + SO_Wasserwirtschaft +from SAGisXPlanung.SonstigePlanwerke.SO_Schutzgebiete import SO_SchutzgebietWasserrecht +from SAGisXPlanung.XPlan.XP_Praesentationsobjekte.feature_types import XP_AbstraktesPraesentationsobjekt, XP_PPO, XP_PTO, \ + XP_Nutzungsschablone +from SAGisXPlanung.XPlan.data_types import (XP_SpezExterneReferenz, XP_ExterneReferenz, XP_VerfahrensMerkmal, + XP_Gemeinde, + XP_Plangeber, XP_GesetzlicheGrundlage, XP_SPEMassnahmenDaten, + XP_Hoehenangabe) +from SAGisXPlanung.XPlan.feature_types import XP_Plan, XP_Bereich, XP_Objekt +from SAGisXPlanung.XPlan.simple_depth import XP_SimpleGeometry + +logger = logging.getLogger(__name__) + +CLASSES = { + 'XP_Plan': XP_Plan, + 'BP_Plan': BP_Plan, + 'FP_Plan': FP_Plan, + 'RP_Plan': RP_Plan, + 'LP_Plan': LP_Plan, + 'XP_Bereich': XP_Bereich, + 'FP_Bereich': FP_Bereich, + 'BP_Bereich': BP_Bereich, + 'RP_Bereich': RP_Bereich, + 'LP_Bereich': LP_Bereich, + + 'XP_Objekt': XP_Objekt, + 'XP_ExterneReferenz': XP_ExterneReferenz, + 'XP_Gemeinde': XP_Gemeinde, + 'XP_Plangeber': XP_Plangeber, + 'XP_GesetzlicheGrundlage': XP_GesetzlicheGrundlage, + 'XP_VerfahrensMerkmal': XP_VerfahrensMerkmal, + 'XP_SpezExterneReferenz': XP_SpezExterneReferenz, + 'XP_SPEMassnahmenDaten': XP_SPEMassnahmenDaten, + 'XP_AbstraktesPraesentationsobjekt': XP_AbstraktesPraesentationsobjekt, + 'XP_PPO': XP_PPO, + 'XP_PTO': XP_PTO, + 'XP_Nutzungsschablone': XP_Nutzungsschablone, + 'XP_Hoehenangabe': XP_Hoehenangabe, + 'XP_SimpleGeometry': XP_SimpleGeometry, + + 'BP_Objekt': BP_Objekt, + 'BP_FlaecheOhneFestsetzung': BP_FlaecheOhneFestsetzung, + 'BP_BesondererNutzungszweckFlaeche': BP_BesondererNutzungszweckFlaeche, + 'BP_GemeinbedarfsFlaeche': BP_GemeinbedarfsFlaeche, + 'BP_SpielSportanlagenFlaeche': BP_SpielSportanlagenFlaeche, + 'BP_GruenFlaeche': BP_GruenFlaeche, + 'BP_LandwirtschaftsFlaeche': BP_LandwirtschaftsFlaeche, + 'BP_WaldFlaeche': BP_WaldFlaeche, + 'BP_StrassenVerkehrsFlaeche': BP_StrassenVerkehrsFlaeche, + 'BP_VerkehrsflaecheBesondererZweckbestimmung': BP_VerkehrsflaecheBesondererZweckbestimmung, + 'BP_StrassenbegrenzungsLinie': BP_StrassenbegrenzungsLinie, + 'BP_GewaesserFlaeche': BP_GewaesserFlaeche, + 'BP_VerEntsorgung': BP_VerEntsorgung, + 'BP_AnpflanzungBindungErhaltung': BP_AnpflanzungBindungErhaltung, + 'BP_SchutzPflegeEntwicklungsFlaeche': BP_SchutzPflegeEntwicklungsFlaeche, + 'BP_Wegerecht': BP_Wegerecht, + 'BP_BaugebietsTeilFlaeche': BP_BaugebietsTeilFlaeche, + 'BP_BauGrenze': BP_BauGrenze, + 'BP_BauLinie': BP_BauLinie, + 'BP_Dachgestaltung': BP_Dachgestaltung, + 'BP_KomplexeZweckbestGruen': BP_KomplexeZweckbestGruen, + 'BP_KomplexeZweckbestSpielSportanlage': BP_KomplexeZweckbestSpielSportanlage, + 'BP_KomplexeZweckbestGemeinbedarf': BP_KomplexeZweckbestGemeinbedarf, + 'BP_KomplexeZweckbestLandwirtschaft': BP_KomplexeZweckbestLandwirtschaft, + 'BP_KomplexeZweckbestWald': BP_KomplexeZweckbestWald, + 'BP_KomplexeZweckbestVerEntsorgung': BP_KomplexeZweckbestVerEntsorgung, + 'BP_KomplexeSondernutzung': BP_KomplexeSondernutzung, + 'BP_VeraenderungssperreDaten': BP_VeraenderungssperreDaten, + 'BP_NutzungsartenGrenze': BP_NutzungsartenGrenze, + 'BP_BereichOhneEinAusfahrtLinie': BP_BereichOhneEinAusfahrtLinie, + 'BP_EinfahrtPunkt': BP_EinfahrtPunkt, + + 'FP_Objekt': FP_Objekt, + 'FP_BebauungsFlaeche': FP_BebauungsFlaeche, + 'FP_KomplexeSondernutzung': FP_KomplexeSondernutzung, + 'FP_Gemeinbedarf': FP_Gemeinbedarf, + 'FP_SpielSportanlage': FP_SpielSportanlage, + 'FP_Gruen': FP_Gruen, + 'FP_Landwirtschaft': FP_Landwirtschaft, + 'FP_WaldFlaeche': FP_WaldFlaeche, + 'FP_Strassenverkehr': FP_Strassenverkehr, + 'FP_Gewaesser': FP_Gewaesser, + 'FP_KomplexeZweckbestGemeinbedarf': FP_KomplexeZweckbestGemeinbedarf, + 'FP_KomplexeZweckbestSpielSportanlage': FP_KomplexeZweckbestSpielSportanlage, + 'FP_KomplexeZweckbestGruen': FP_KomplexeZweckbestGruen, + 'FP_KomplexeZweckbestLandwirtschaft': FP_KomplexeZweckbestLandwirtschaft, + 'FP_KomplexeZweckbestWald': FP_KomplexeZweckbestWald, + + 'SO_Objekt': SO_Objekt, + 'SO_Schienenverkehrsrecht': SO_Schienenverkehrsrecht, + 'SO_Denkmalschutzrecht': SO_Denkmalschutzrecht, + 'SO_SchutzgebietWasserrecht': SO_SchutzgebietWasserrecht, + 'SO_Wasserwirtschaft': SO_Wasserwirtschaft, + 'SO_Gewaesser': SO_Gewaesser, + 'SO_KomplexeFestlegungGewaesser': SO_KomplexeFestlegungGewaesser, + 'SO_Strassenverkehr': SO_Strassenverkehr, + 'SO_KomplexeZweckbestStrassenverkehr': SO_KomplexeZweckbestStrassenverkehr +} + +PRE_FILLED_CLASSES = [ + XP_Gemeinde, + XP_Plangeber, + XP_GesetzlicheGrundlage +] + +OBJECT_BASE_TYPES = [ + BP_Objekt, FP_Objekt, SO_Objekt +] + + +def save_to_db(obj, expire_on_commit=True): + """ Fügt ein Objekt der Datenbank hinzu """ + try: + with Session.begin() as session: + session.expire_on_commit = expire_on_commit + session.add(obj) + except Exception as e: + logger.exception(e) + + +async def save_to_db_async(obj): + """ Async Version der save_to_db Methode""" + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, save_to_db, obj) + + +def query_existing(obj): + """ + Prüft ob ein gegebenes XPlanung-Objekt bereits in der Datenbank existiert. + Falls ja, wird dieses zurückgegeben, ansonsten None + """ + with Session.begin() as session: + session.expire_on_commit = False + objects_from_db = session.query(obj.__class__).all() + obj_from_db = next((x for x in objects_from_db if x == obj), None) + return obj_from_db + + +def createXPlanungIndicators(): + xp_indicator = QgsLayerTreeViewIndicator(iface.layerTreeView()) + xp_indicator.setToolTip('Diese Gruppe stellt ein XPlanung konform erfasstes Planwerk dar.') + xp_indicator.setIcon(QtGui.QIcon(os.path.abspath(os.path.join(os.path.dirname(__file__), + 'gui/resources/xplanung_icon.png')))) + reload_indicator = QgsLayerTreeViewIndicator(iface.layerTreeView()) + reload_indicator.setToolTip('Planwerk aktualisieren') + reload_indicator.setIcon(QtGui.QIcon(':/images/themes/default/mActionRefresh.svg')) + + return xp_indicator, reload_indicator + + +def confirmObjectDeletion(obj) -> bool: + """ + Generiert eine MessageBox zur Bestätigung des Löschens eines beliebigen XPlanung-Objekts. + Überprüft dabei ob abhängige Objekte bestehen. + + Returns + ------- + bool: + True, wenn Löschen bestätigt wurde; + False, wenn Vorgang abgebrochen wurde + """ + msg = QMessageBox() + msg.setIcon(QMessageBox.Warning) + + has_dependencies = False + for rel in obj.__class__.relationships(): + if getattr(obj, rel[0]): + has_dependencies = True + break + if not has_dependencies: + msg.setText(f"Wollen Sie das Objekt unwideruflich löschen?" + f"
    • ID: {obj.id}
    • " + f"
    • Objektklasse: {obj.__class__.__name__}
    ") + else: + msg.setText(f"Objekt besitzt andere abhängige Objekte. Trotzdem Löschen?" + f"
    • ID: {obj.id}
    • " + f"
    • Objektklasse: {obj.__class__.__name__}
    ") + msg.setWindowTitle("Löschvorgang bestätigen") + msg.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel) + msg.setDefaultButton(QMessageBox.Cancel) + ret = msg.exec_() + if ret == QMessageBox.Cancel: + return False + + return True + + +def full_version_required_warning(): + msg = QMessageBox() + msg.setIcon(QMessageBox.Warning) + msg.setText(f"Diese Funktion ist nur in der Vollversion verfügbar." + f"
    " + f"Bei Interesse an der Vollversion, wenden Sie sich an info-de@nti.biz.") + msg.setWindowTitle("Funktion nicht verfügbar.") + msg.setStandardButtons(QMessageBox.Ok) + msg.setDefaultButton(QMessageBox.Ok) + msg.exec_() + + +def is_url(url): + try: + result = urlparse(url) + return all([result.scheme, result.netloc]) + except ValueError: + return False diff --git a/tests/ContextMenuTool_test.py b/tests/ContextMenuTool_test.py new file mode 100644 index 0000000..2886167 --- /dev/null +++ b/tests/ContextMenuTool_test.py @@ -0,0 +1,90 @@ +import uuid + +import pytest +from geoalchemy2 import WKTElement +from qgis.PyQt import QtTest + +from qgis.core import QgsVectorLayer, QgsFeature, QgsGeometry, QgsAnnotationLayer, QgsRenderedAnnotationItemDetails, QgsProject +from qgis.gui import QgsHighlight, QgsMapToolIdentify +from qgis.utils import iface + +from SAGisXPlanung.Tools.ContextMenuTool import ContextMenuTool, ActionType +from SAGisXPlanung.XPlan.XP_Praesentationsobjekte.feature_types import XP_PTO +from SAGisXPlanung.XPlanungItem import XPlanungItem + + +@pytest.fixture() +def tool(mocker) -> ContextMenuTool: + return ContextMenuTool(iface.mapCanvas(), None) + + +@pytest.fixture() +def feat(mocker) -> QgsFeature: + feat = QgsFeature() + g = QgsGeometry.fromWkt('Point(10 10)') + feat.setGeometry(g) + return feat + + +@pytest.fixture() +def vl(feat) -> QgsVectorLayer: + layer = QgsVectorLayer('point?crs=epsg:4326', "Scratch point layer", "memory") + dp = layer.dataProvider() + layer.startEditing() + _, newFeatures = dp.addFeatures([feat]) + layer.commitChanges() + return layer + + +@pytest.fixture() +def al() -> QgsAnnotationLayer: + tpo = XP_PTO() + tpo.id = uuid.uuid4() + tpo.position = WKTElement('POINT (1 1)', srid=25833) + tpo.schriftinhalt = 'test' + tpo.skalierung = 0.5 + tpo.drehwinkel = 0 + layer: QgsAnnotationLayer = tpo.asLayer(tpo.position.srid, uuid.uuid4(), 'TestLayer') + item = tpo.asFeature() + layer.addItem(item) + return layer + + +class TestContextMenuTool: + + def delete_highlight(self, tool: ContextMenuTool, vl, feat): + tool.highlight = QgsHighlight(iface.mapCanvas(), feat, vl) + tool.delete_highlight() + assert tool.highlight is None + assert len(tool.canvas.scene().items()) == 0 + + def test_menu_action_hovered(self, tool: ContextMenuTool, vl, feat): + prev = len(tool.canvas.scene().items()) + tool.menu_action_hovered(vl, feat) + assert tool.highlight + assert len(tool.canvas.scene().items()) == prev + 1 + + def test_menu_action_accessAttributes(self, tool: ContextMenuTool): + spy = QtTest.QSignalSpy(tool.accessAttributesRequested) + tool.menu_action_triggered(ActionType.AccessAttributes, XPlanungItem(xid='1', xtype=None, plan_xid='2')) + assert len(spy) == 1 + + def test_menu_action_highlightObjectTree(self, tool: ContextMenuTool): + spy = QtTest.QSignalSpy(tool.highlightObjectTreeRequested) + tool.menu_action_triggered(ActionType.HighlightObjectTreeItem, XPlanungItem(xid='1', xtype=None, plan_xid='2')) + assert len(spy) == 1 + + def test_menu(self, tool: ContextMenuTool, vl, feat, al): + xplan_layer = vl.clone() + xplan_layer.setCustomProperty('xplanung/type', 'BP_BauGrenze') + xplan_layer.setCustomProperty('xplanung/plan-name', 'Plan1') + xplan_layer.setCustomProperty('xplanung/feat-1', '14356316-413643-46136-413') + results = [QgsMapToolIdentify.IdentifyResult(vl, feat, {'test': '1'}), + QgsMapToolIdentify.IdentifyResult(xplan_layer, feat, {'test': '2'})] + QgsProject().instance().addMapLayer(al) + + a_items = [QgsRenderedAnnotationItemDetails(al.id(), item_id) for item_id in al.items().keys()] + menu = tool.menu(results, annotation_items=a_items) + + assert len(menu.actions()) == 3 + assert len(menu.actions()[0].menu().actions()) == 3 diff --git a/tests/ConverterTask_test.py b/tests/ConverterTask_test.py new file mode 100644 index 0000000..d47e216 --- /dev/null +++ b/tests/ConverterTask_test.py @@ -0,0 +1,9 @@ +import os + +from qgis.core import QgsApplication + + +class Test_ConverterTasks: + # TODO: converter task tests + pass + diff --git a/tests/GMLReader_test.py b/tests/GMLReader_test.py new file mode 100644 index 0000000..f86918b --- /dev/null +++ b/tests/GMLReader_test.py @@ -0,0 +1,114 @@ +import datetime +import os +import pytest +from lxml import etree +from geoalchemy2 import WKTElement, WKBElement + +from SAGisXPlanung.BPlan.BP_Bebauung.feature_types import BP_BaugebietsTeilFlaeche +from SAGisXPlanung.GML.GMLReader import GMLReader +from SAGisXPlanung.XPlan.XP_Praesentationsobjekte.feature_types import XP_PPO, XP_PTO, XP_Nutzungsschablone +from SAGisXPlanung.XPlan.data_types import XP_Gemeinde, XP_Plangeber +from SAGisXPlanung.XPlan.enums import XP_ExterneReferenzTyp, XP_ExterneReferenzArt + + +def side_effect(*args): + if args[0].__class__ == XP_Gemeinde: + return XP_Gemeinde() + elif args[0].__class__ == XP_Plangeber: + return XP_Plangeber() + + +@pytest.fixture +def gml_reader(request, mocker): + mocker.patch( + 'SAGisXPlanung.GML.GMLReader.query_existing', + side_effect=side_effect + ) + gml = etree.parse(os.path.join(os.path.dirname(__file__), f'gml/{request.param}')) + return GMLReader(etree.tostring(gml)) + + +class TestGMLReader_read_data_object: + + @pytest.mark.parametrize('gml_reader', ['bp_plan.gml'], indirect=True) + def test_read_data_object(self, gml_reader): + string = '' \ + '4326436' \ + 'Berlin' \ + '' + gml = etree.fromstring(string) + + obj = GMLReader.read_data_object(gml) + + assert obj.__class__.__name__ == "XP_Gemeinde" + assert obj.ags == '4326436' + assert obj.gemeindeName == 'Berlin' + + @pytest.mark.parametrize('gml_reader', ['bp_plan.gml'], indirect=True) + def test_read_data_object_enum(self, gml_reader): + string = '' \ + 'Dokument' \ + 'ref1' \ + r'D:\Downloads\document.pdf' \ + 'application/pdf' \ + '2021-08-19' \ + '1000' \ + '' + + gml = etree.fromstring(string) + + obj = GMLReader.read_data_object(gml) + + assert obj.__class__.__name__ == "XP_SpezExterneReferenz" + assert obj.art == XP_ExterneReferenzArt.Dokument + assert obj.typ == XP_ExterneReferenzTyp.Beschreibung + + +class TestGMLReader_readPlan: + + @pytest.mark.parametrize('gml_reader', ['bp_plan.gml'], indirect=True) + def test_readPlan(self, gml_reader): + plan = gml_reader.plan + assert plan.name == 'bp_plan' + assert isinstance(plan.technHerstellDatum, datetime.date) + assert plan.technHerstellDatum.strftime('%Y-%m-%d') == '2021-06-04' + assert len(plan.auslegungsStartDatum) == 2 + assert len(plan.externeReferenz) == 1 + assert plan.externeReferenz[0].referenzName == 'ref1' + + assert len(plan.bereich) == 2 + assert len(plan.bereich[1].planinhalt) == 3 + assert plan.bereich[1].planinhalt[1].__class__.__name__ == 'BP_Wegerecht' + assert len(plan.bereich[1].planinhalt[1].typ) == 2 + + baugebiet = plan.bereich[1].planinhalt[2] + assert isinstance(baugebiet, BP_BaugebietsTeilFlaeche) + assert len(baugebiet.wirdDargestelltDurch) == 3 + assert isinstance(baugebiet.wirdDargestelltDurch[0], XP_Nutzungsschablone) + assert not baugebiet.wirdDargestelltDurch[0].hidden + assert isinstance(baugebiet.wirdDargestelltDurch[1], XP_PPO) + assert baugebiet.wirdDargestelltDurch[1].drehwinkel == '4.20' + assert isinstance(baugebiet.wirdDargestelltDurch[2], XP_PTO) + assert baugebiet.wirdDargestelltDurch[2].schriftinhalt == '(B)' + + @pytest.mark.parametrize('gml_reader', ['bp_plan1.gml'], indirect=True) + def test_readPlan_top_level_ns_issue24(self, gml_reader): + plan = gml_reader.plan + assert plan.name == 'Denkmalbereichssatzung Eisenbahnersiedlung' + assert len(plan.externeReferenz) == 1 + assert plan.externeReferenz[0].referenzName == 'Satzung Denkmalbereichssatzung "Eisenbahnersiedlung"' + + +class TestGMLReader_readGeometries: + + @pytest.mark.parametrize('gml_reader', ['bp_plan.gml'], indirect=True) + def test_readGeometry(self, gml_reader: GMLReader): + string = '' \ + '571665.1779 5940876.1455' \ + '' + gml = etree.fromstring(string) + + wkt_element = gml_reader.readGeometry(gml) + + assert isinstance(wkt_element, WKTElement) + assert wkt_element.data == 'POINT (571665.1779 5940876.1455)' diff --git a/tests/GMLWriter_test.py b/tests/GMLWriter_test.py new file mode 100644 index 0000000..07ced9a --- /dev/null +++ b/tests/GMLWriter_test.py @@ -0,0 +1,170 @@ +import datetime +import os +import uuid +import zipfile +from io import BytesIO +from pathlib import PurePath + +import pytest +from geoalchemy2 import WKTElement, WKBElement +from geoalchemy2.shape import from_shape, to_shape +from lxml import etree +from shapely.geometry import Polygon, MultiPolygon, MultiLineString, Point + +from SAGisXPlanung.BPlan.BP_Basisobjekte.enums import BP_PlanArt, BP_Rechtscharakter +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Plan, BP_Bereich +from SAGisXPlanung.BPlan.BP_Bebauung.data_types import BP_Dachgestaltung +from SAGisXPlanung.BPlan.BP_Bebauung.enums import BP_Dachform +from SAGisXPlanung.BPlan.BP_Bebauung.feature_types import BP_BaugebietsTeilFlaeche, BP_BauGrenze +from SAGisXPlanung.BPlan.BP_Landwirtschaft_Wald_und_Gruenflaechen.feature_types import BP_GruenFlaeche +from SAGisXPlanung.BPlan.BP_Naturschutz_Landschaftsbild_Naturhaushalt.feature_types import BP_AnpflanzungBindungErhaltung +from SAGisXPlanung.BPlan.BP_Sonstiges.enums import BP_WegerechtTypen +from SAGisXPlanung.BPlan.BP_Sonstiges.feature_types import BP_Wegerecht +from SAGisXPlanung.FPlan.FP_Basisobjekte.enums import FP_Rechtscharakter +from SAGisXPlanung.FPlan.FP_Landwirtschaft_Wald_und_Gruen.feature_types import FP_WaldFlaeche +from SAGisXPlanung.GML.GMLWriter import writeTextNode, GMLWriter +from SAGisXPlanung.XPlan.XP_Praesentationsobjekte.feature_types import XP_Nutzungsschablone, XP_PTO +from SAGisXPlanung.XPlan.data_types import XP_Gemeinde, XP_Plangeber, XP_SpezExterneReferenz, XP_ExterneReferenz, \ + XP_Hoehenangabe +from SAGisXPlanung.XPlan.enums import XP_WaldbetretungTyp, XP_ArtHoehenbezugspunkt + + +@pytest.fixture() +def gml_writer(): + plan = BP_Plan() + plan.raeumlicherGeltungsbereich = WKTElement( + 'MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),(30 20, 20 25, 20 15, 30 20)))', + srid=4326) + plan.name = 'test' + plan.traegerbeteiligungsStartDatum = [datetime.date(2000, 9, 10), datetime.date(2000, 10, 10)] + gemeinde = XP_Gemeinde() + gemeinde.gemeindeName = 'test' + gemeinde.ags = '19613526' + plan.gemeinde.append(gemeinde) + plangeber = XP_Plangeber() + plangeber.name = 'test' + plan.plangeber = plangeber + + ref = XP_SpezExterneReferenz() + ref.beschreibung = 'test' + with open(os.path.join(os.path.dirname(__file__), 'data/bp_plan.tif'), 'rb') as file: + file_bytes = file.read() + ref.file = file_bytes + ref.referenzURL = 'data/bp_plan.tif' + plan.externeReferenz.append(ref) + + ref1 = XP_ExterneReferenz() + ref1.beschreibung = 'test' + ref1.file = file_bytes + + bereich = BP_Bereich() + bereich.nummer = 0 + bereich.name = 'test' + bereich.geltungsbereich = plan.raeumlicherGeltungsbereich + bereich.refScan.append(ref1) + + bp_objekt_poly = BP_BaugebietsTeilFlaeche() + bp_objekt_poly.position = WKBElement(to_shape(bereich.geltungsbereich).wkb, srid=4326) + bp_objekt_poly.GRZ = 0.4 + bp_objekt_poly.wirdDargestelltDurch[0].position = from_shape(Point(0, 0)) + bp_dach = BP_Dachgestaltung() + bp_dach.dachform = BP_Dachform.Walmdach + bp_dach.DN = 20 + bp_objekt_poly.dachgestaltung.append(bp_dach) + hoehenangabe = XP_Hoehenangabe() + hoehenangabe.bezugspunkt = XP_ArtHoehenbezugspunkt.TH + hoehenangabe.h = 4.5 + bp_objekt_poly.hoehenangabe.append(hoehenangabe) + pto = XP_PTO() + pto.position = WKTElement('POINT (1 1)', srid=25833) + pto.schriftinhalt = 'test' + pto.drehwinkel = 45 + pto.skalierung = 0.25 + bp_objekt_poly.wirdDargestelltDurch.append(pto) + bereich.planinhalt.append(bp_objekt_poly) + + point_object = BP_AnpflanzungBindungErhaltung() + point_object.position = from_shape(Point(0, 0)) + bereich.planinhalt.append(point_object) + + bp_objekt_line = BP_BauGrenze() + bp_objekt_line.position = from_shape(MultiLineString([((0, 0), (1, 1)), ((-1, 0), (1, 0))])) + bp_objekt_line.bautiefe = 5.3 + bp_objekt_line.aufschrift = 'baugrenze' + bereich.planinhalt.append(bp_objekt_line) + + bp_wegerecht = BP_Wegerecht() + bp_wegerecht.position = plan.raeumlicherGeltungsbereich + bp_wegerecht.typ = [BP_WegerechtTypen.Gehrecht, BP_WegerechtTypen.Fahrrecht] + bereich.planinhalt.append(bp_wegerecht) + + o_with_empty_array = BP_Wegerecht() + o_with_empty_array.position = plan.raeumlicherGeltungsbereich + o_with_empty_array.typ = [] + bereich.planinhalt.append(o_with_empty_array) + + plan.bereich.append(bereich) + return GMLWriter(plan) + + +@pytest.fixture() +def bplan_schema(): + xmlschema_doc = etree.parse(os.path.join(os.path.dirname(__file__), 'xsd/XPlanGML_BPlan.xsd')) + return etree.XMLSchema(xmlschema_doc) + + +@pytest.fixture() +def fplan_schema(): + xmlschema_doc = etree.parse(os.path.join(os.path.dirname(__file__), 'xsd/XPlanGML_FPlan.xsd')) + return etree.XMLSchema(xmlschema_doc) + + +@pytest.fixture() +def xplan_schema(): + xmlschema_doc = etree.parse(os.path.join(os.path.dirname(__file__), 'xsd/XPlanung-Operationen.xsd')) + return etree.XMLSchema(xmlschema_doc) + + +@pytest.fixture() +def gml_schema(): + xmlschema_doc = etree.parse(os.path.join(os.path.dirname(__file__), 'xsd/gmlProfile/gmlProfilexplan.xsd')) + return etree.XMLSchema(xmlschema_doc) + + +class TestGMLWriter_writeTextNode: + + @pytest.mark.parametrize('text,expected', [(BP_PlanArt.BPlan, '1000'), + ('BP_Plan2070', 'BP_Plan2070'), + (1000, '1000'), + (False, 'false')]) + def test_writeTextNode(self, text, expected): + result = writeTextNode(text) + assert result == expected + + +class TestGMLWriter_writeSubObject: + + def test_writeSubObject(self, gml_writer, xplan_schema): + obj = XP_Gemeinde() + obj.ags = "12345678" + obj.gemeindeName = "Berlin" + + root = gml_writer.writeSubObject(obj) + + assert xplan_schema.validate(root) + + +class TestGMLWriter_export: + + def test_root_valid(self, gml_writer, xplan_schema): + xplan_schema.assertValid(gml_writer.root[-1:][0]) + + def test_toArchive(self, gml_writer): + + archive = gml_writer.toArchive() + zip_archive = zipfile.ZipFile(archive) + + assert type(archive) == BytesIO + assert len(zip_archive.namelist()) == 2 + assert any(PurePath(file_name).suffix == '.gml' for file_name in zip_archive.namelist()) + assert any(PurePath(file_name).suffix == '.tif' for file_name in zip_archive.namelist()) diff --git a/tests/IdentifyFeatureTool_test.py b/tests/IdentifyFeatureTool_test.py new file mode 100644 index 0000000..9b5f60a --- /dev/null +++ b/tests/IdentifyFeatureTool_test.py @@ -0,0 +1,35 @@ +import pytest + +from qgis.utils import iface +from qgis.PyQt.QtCore import Qt, QPoint + +from SAGisXPlanung.Tools.IdentifyFeatureTool import IdentifyFeatureTool + + +@pytest.fixture() +def map_tool(): + return IdentifyFeatureTool(iface.mapCanvas()) + + +class TestIdentifyFeatureTool_canvas_click: + + def test_maptool_canvas_release(self, map_tool, qtbot): + canvas = iface.mapCanvas() + canvas.setMapTool(map_tool) + assert canvas.mapTool() == map_tool + + # map_tool.canvasReleaseEvent = MagicMock() + qtbot.addWidget(canvas) + qtbot.mouseRelease(canvas, Qt.LeftButton, pos=QPoint(int(canvas.center().x()), int(canvas.center().y()))) + # sleep(1) + # assert map_tool.canvasReleaseEvent.called + + def test_maptool_canvas_release_right(self, map_tool, qtbot): + canvas = iface.mapCanvas() + canvas.setMapTool(map_tool) + assert canvas.mapTool() == map_tool + + qtbot.addWidget(canvas) + qtbot.mouseRelease(canvas, Qt.RightButton) + # sleep(1) + # assert canvas.mapTool() != map_tool diff --git a/tests/MapLayerRegistry_test.py b/tests/MapLayerRegistry_test.py new file mode 100644 index 0000000..39f0101 --- /dev/null +++ b/tests/MapLayerRegistry_test.py @@ -0,0 +1,159 @@ +import uuid + +import pytest +from geoalchemy2 import WKTElement + +from qgis.core import QgsVectorLayer, QgsAnnotationLayer, QgsProject, QgsGeometry, QgsWkbTypes + +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Plan +from SAGisXPlanung.BPlan.BP_Naturschutz_Landschaftsbild_Naturhaushalt.feature_types import BP_AnpflanzungBindungErhaltung +from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry +from SAGisXPlanung.XPlan.XP_Praesentationsobjekte.feature_types import XP_PTO +from SAGisXPlanung.XPlanungItem import XPlanungItem + + +plan_xid = 'c52aeb9d-34e2-4eca-b56b-e3f3752c94dd' +feat_xid = 'd52aeb9d-34e2-4eca-b56b-e3f3752c94dd' +feat1_xid = 'e52aeb9d-34e2-4eca-b56b-e3f3752c94dd' +feat2_xid = 'f52aeb9d-34e2-4eca-b56b-e3f3752c94dd' + + +@pytest.fixture() +def xitem() -> XPlanungItem: + return XPlanungItem(xid=str(uuid.uuid4()), xtype=XP_PTO, plan_xid=plan_xid) + + +@pytest.fixture() +def vl() -> QgsVectorLayer: + layer = QgsVectorLayer('polygon?crs=epsg:4326', "Scratch layer", "memory") + layer.setCustomProperty('xplanung/type', 'BP_BaugebietsTeilFlaeche') + layer.setCustomProperty('xplanung/plan-xid', plan_xid) + layer.setCustomProperty(f'xplanung/feat-1', feat_xid) + return layer + + +@pytest.fixture() +def vl1() -> QgsVectorLayer: + layer = QgsVectorLayer('point?crs=epsg:4326', "Scratch layer", "memory") + layer.setCustomProperty('xplanung/type', 'BP_AnpflanzungBindungErhaltung') + layer.setCustomProperty('xplanung/plan-xid', plan_xid) + layer.setCustomProperty(f'xplanung/feat-1', feat1_xid) + return layer + + +@pytest.fixture() +def vl2() -> QgsVectorLayer: + layer = QgsVectorLayer('polygon?crs=epsg:4326', "Scratch layer", "memory") + layer.setCustomProperty('xplanung/type', 'BP_AnpflanzungBindungErhaltung') + layer.setCustomProperty('xplanung/plan-xid', plan_xid) + layer.setCustomProperty(f'xplanung/feat-1', feat2_xid) + return layer + + +@pytest.fixture() +def al(xitem) -> QgsVectorLayer: + tpo = XP_PTO() + tpo.id = xitem.xid + tpo.position = WKTElement('POINT (1 1)', srid=25833) + tpo.schriftinhalt = 'test' + return tpo.asLayer(tpo.position.srid, xitem.plan_xid, 'TestLayer') + + +@pytest.fixture(scope="session") +def registry() -> MapLayerRegistry: + reg = MapLayerRegistry() + return reg + + +@pytest.fixture(autouse=True) +def clear_registry_after_test(registry): + yield + + registry._layers = [] + QgsProject().instance().removeAllMapLayers() + + +class TestMapLayerRegistry: + + def test_add_layer(self, registry, vl, al): + registry.addLayer(vl) + registry.addLayer(al) + registry.addLayer(al) + + assert al in registry.layers + assert vl in registry.layers + # verify that duplicate isn't added to registry + assert len(registry.layers) == 2 + assert len(QgsProject().instance().mapLayersByName("Scratch layer")) == 1 + + def test_add_layer_into_group(self, registry, vl, al): + root = QgsProject.instance().layerTreeRoot() + layer_group = root.addGroup("Test_Group") + + registry.addLayer(vl, layer_group) + registry.addLayer(al, layer_group) + + assert len(layer_group.children()) == 2 + assert layer_group.children()[0].layerId() == al.id() + assert layer_group.children()[1].layerId() == vl.id() + + def test_remove_layer(self, registry, vl, al): + registry.addLayer(vl) + + # remove non existent layer + registry.removeLayer(al.id()) + # remove valid layer + registry.removeLayer(vl.id()) + + assert len(registry.layers) == 0 + + def test_get_layer_by_xid(self, registry, al, xitem): + registry.addLayer(al) + + layer = registry.layerByXid(xitem) + + assert isinstance(layer, QgsAnnotationLayer) + + def test_get_layer_by_xid_with_type(self, registry, vl1, vl2): + registry.addLayer(vl1) + registry.addLayer(vl2) + + xitem = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_AnpflanzungBindungErhaltung, plan_xid=plan_xid) + layer = registry.layerByXid(xitem, QgsWkbTypes.PointGeometry) + + assert isinstance(layer, QgsVectorLayer) + assert layer.geometryType() == QgsWkbTypes.PointGeometry + + def test_feature_is_shown(self, registry, vl): + registry.addLayer(vl) + + assert registry.featureIsShown(feat_xid) + assert not registry.featureIsShown(plan_xid) + + def test_layer_by_feature(self, registry, vl): + registry.addLayer(vl) + + assert registry.layerByFeature(feat_xid) + assert registry.layerByFeature(plan_xid) is None + + def test_geometries_changed(self, mocker, registry, vl): + mocker.patch("SAGisXPlanung.MapLayerRegistry.MapLayerRegistry.layerById").return_value = vl + + poly = BP_Plan() + poly.position = WKTElement('MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)))', srid=25833) + session_mock = mocker.MagicMock() + obj_mock = mocker.MagicMock() + session_mock.query.return_value.get.return_value = obj_mock + mocker.patch("SAGisXPlanung.Session.begin").return_value.__enter__.return_value = session_mock + + registry.onGeometriesChanged(vl.id(), {1: QgsGeometry()}) + + obj_mock.setGeometry.assert_not_called() + + def test_add_canvas_item(self, mocker, registry): + canvas_item_mock = mocker.patch("SAGisXPlanung.BuildingTemplateItem.BuildingTemplateItem").return_value + + registry.addCanvasItem(canvas_item_mock, feat_xid) + + assert len(registry._canvasItems) == 1 + assert registry.canvasItemsAtFeat(feat_xid)[0] == canvas_item_mock diff --git a/tests/QCollapsibleSearch_test.py b/tests/QCollapsibleSearch_test.py new file mode 100644 index 0000000..7540b45 --- /dev/null +++ b/tests/QCollapsibleSearch_test.py @@ -0,0 +1,32 @@ +import pytest + +from qgis.PyQt.QtCore import QEvent +from qgis.PyQt.QtGui import QFocusEvent + +from SAGisXPlanung.gui.widgets.QCollapsibleSearch import QCollapsibleSearch + + +@pytest.fixture() +def search_widget(): + return QCollapsibleSearch() + + +class TestQCollapsibleSearch: + + async def test_expand_and_shrink(self, search_widget, qtbot): + qtbot.addWidget(search_widget) + assert not search_widget.property('expanded') + + search_widget.search_icon_action.trigger() + assert search_widget.property('expanded') + + search_widget.setText('search-query') + search_widget.focusOutEvent(QFocusEvent(QEvent.FocusOut)) + assert search_widget.property('expanded') + + search_widget.setText('') + search_widget.focusOutEvent(QFocusEvent(QEvent.FocusOut)) + assert not search_widget.property('expanded') + + + diff --git a/tests/QCustomTreeWidgets_test.py b/tests/QCustomTreeWidgets_test.py new file mode 100644 index 0000000..008b3b7 --- /dev/null +++ b/tests/QCustomTreeWidgets_test.py @@ -0,0 +1,28 @@ +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Plan +from SAGisXPlanung.FPlan.FP_Basisobjekte.feature_types import FP_Plan +from SAGisXPlanung.gui.widgets.QCustomTreeWidgets import QObjectTypeSelectionTreeWidget + + +class TestQObjectTypeSelectionTreeWidget: + + def test_setup_with_bp(self): + widget = QObjectTypeSelectionTreeWidget() + + assert widget.topLevelItemCount() == 0 + assert widget.columnCount() == 1 + + widget.setup(BP_Plan) + + assert widget.topLevelItemCount() + assert widget.topLevelItem(0).text(0).startswith('BP') + + def test_setup_with_fp(self): + widget = QObjectTypeSelectionTreeWidget() + + assert widget.topLevelItemCount() == 0 + assert widget.columnCount() == 1 + + widget.setup(FP_Plan) + + assert widget.topLevelItemCount() + assert widget.topLevelItem(0).text(0).startswith('FP') diff --git a/tests/QExplorerView_test.py b/tests/QExplorerView_test.py new file mode 100644 index 0000000..f588f87 --- /dev/null +++ b/tests/QExplorerView_test.py @@ -0,0 +1,273 @@ +import uuid + +import pytest + +from qgis.PyQt.QtCore import QModelIndex, QItemSelection +from qgis.PyQt.QtTest import QSignalSpy + +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Plan +from SAGisXPlanung.BPlan.BP_Bebauung.data_types import BP_Dachgestaltung +from SAGisXPlanung.BPlan.BP_Bebauung.feature_types import BP_BaugebietsTeilFlaeche +from SAGisXPlanung.XPlan.data_types import XP_VerfahrensMerkmal +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui.style import FlagNewRole +from SAGisXPlanung.gui.widgets.QExplorerView import ExplorerTreeModel, ClassNode, QExplorerView, XID_ROLE + +xid = 'a8a1beff-0057-47e1-b855-4be26fdfa4fb' + +@pytest.fixture() +def xplan_item(): + item = XPlanungItem(xid=xid, xtype=BP_Plan) + return item + + +@pytest.fixture() +def view(): + return QExplorerView() + + +class TestQExplorerView: + + def test_inserting(self, xplan_item, view, qtmodeltester): + model = view.model + + node = ClassNode(xplan_item, new=True) + model.addChild(node) + + assert model._root.childCount() == 1 + assert model.data(model.index(0, 0)) == 'BP_Plan' + assert model.data(model.index(0, 0), FlagNewRole) + assert model.data(model.index(0, 0), XID_ROLE) == xid + qtmodeltester.check(model) + + def test_adding_multiple_clearing(self, xplan_item, view, qtmodeltester): + model = view.model + + node1 = ClassNode(xplan_item) + node2 = ClassNode(xplan_item) + model.addChild(node1) + model.addChild(node2, node1) + + qtmodeltester.check(model) + assert model._root.childCount() == 1 + assert node1.row() == 0 + assert node1.childCount() == 1 + + view.clear() + assert model._root.childCount() == 0 + qtmodeltester.check(model) + + def test_adding_multiple_remove(self, xplan_item, view, qtmodeltester): + model = view.model + + node1 = ClassNode(xplan_item) + node2 = ClassNode(xplan_item) + node3 = ClassNode(xplan_item) + node4 = ClassNode(xplan_item) + model.addChild(node1) + model.addChild(node3) + model.addChild(node2, node1) + model.addChild(node4, node1) + + qtmodeltester.check(model) + assert model._root.childCount() == 2 + assert node3.row() == 1 + assert node1.childCount() == 2 + assert node2.row() == 0 + assert node4.row() == 1 + + spy = QSignalSpy(view.model.rowsRemoved) + view.removeItem(node2) + assert len(spy) == 1 + assert node1.childCount() == 1 + assert node2.parent() is None + assert node4.row() == 0 + qtmodeltester.check(model) + + def test_sorting_alphabetically(self, view, qtmodeltester): + item1 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_Plan) + item2 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_BaugebietsTeilFlaeche) + item3 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_Dachgestaltung) + + node1 = ClassNode(item1) + node2 = ClassNode(item2) + node3 = ClassNode(item3) + view.model.addChild(node1) + view.model.addChild(node2, node1) + + view.sort(1) + + qtmodeltester.check(view.proxy) + qtmodeltester.check(view.proxy.sourceModel()) + assert view.proxy.rowCount(QModelIndex()) == 2 + assert view.proxy.data(view.proxy.index(0, 0)) == 'BP_BaugebietsTeilFlaeche' + assert view.proxy.data(view.proxy.index(1, 0)) == 'BP_Plan' + + # test automatic sorting after adding additional node + view.model.addChild(node3, node2) + + qtmodeltester.check(view.proxy) + qtmodeltester.check(view.proxy.sourceModel()) + assert view.proxy.rowCount(QModelIndex()) == 3 + assert view.proxy.data(view.proxy.index(0, 0)) == 'BP_BaugebietsTeilFlaeche' + assert view.proxy.data(view.proxy.index(1, 0)) == 'BP_Dachgestaltung' + assert view.proxy.data(view.proxy.index(2, 0)) == 'BP_Plan' + + def test_sorting_category(self, view, qtmodeltester): + item1 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_Plan) + item2 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_BaugebietsTeilFlaeche) + item3 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_Dachgestaltung) + + node1 = ClassNode(item1) + node2 = ClassNode(item2) + node3 = ClassNode(item3) + view.model.addChild(node1) + view.model.addChild(node2, node1) + view.model.addChild(node3, node1) + + view.sort(2) + + qtmodeltester.check(view.proxy) + qtmodeltester.check(view.proxy.sourceModel()) + cat_proxy = view.proxy.sourceModel() + assert cat_proxy.rowCount(QModelIndex()) == 2 + first_cat_index = cat_proxy.index(0, 0) + second_cat_index = cat_proxy.index(1, 0) + assert first_cat_index.isValid() + assert second_cat_index.isValid() + assert cat_proxy.data(first_cat_index) == 'BP_Basisobjekte' + assert cat_proxy.data(second_cat_index) == 'BP_Bebauung' + assert cat_proxy.rowCount(first_cat_index) == 1 + assert cat_proxy.rowCount(second_cat_index) == 2 + first_cat_first_child_index = cat_proxy.index(0, 0, first_cat_index) + assert first_cat_first_child_index.isValid() + assert first_cat_first_child_index.data() == 'BP_Plan' + assert first_cat_first_child_index.internalPointer().xplan_module == 'BP_Basisobjekte' + assert first_cat_first_child_index.parent().isValid() + assert first_cat_first_child_index.parent() == first_cat_index + assert first_cat_first_child_index.parent().data() == 'BP_Basisobjekte' + assert not first_cat_first_child_index.parent().parent().isValid() + second_cat_first_child_index = cat_proxy.index(0, 0, second_cat_index) + assert second_cat_first_child_index.isValid() + assert second_cat_first_child_index.internalPointer().xplan_module == 'BP_Bebauung' + assert second_cat_first_child_index.data() == 'BP_BaugebietsTeilFlaeche' + second_cat_second_child_index = cat_proxy.index(1, 0, second_cat_index) + assert second_cat_second_child_index.isValid() + assert second_cat_second_child_index.internalPointer().xplan_module == 'BP_Bebauung' + assert second_cat_second_child_index.data() == 'BP_Dachgestaltung' + + def test_filter_for_id(self, xplan_item, view, qtmodeltester): + xid1 = str(uuid.uuid4()) + xid2 = str(uuid.uuid4()) + item1 = XPlanungItem(xid=xid1, xtype=BP_Plan) + item2 = XPlanungItem(xid=xid2, xtype=BP_BaugebietsTeilFlaeche) + item3 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_Dachgestaltung) + + node1 = ClassNode(item1) + node2 = ClassNode(item2) + node3 = ClassNode(item3) + view.model.addChild(node1) + view.model.addChild(node2, node1) + view.model.addChild(node3, node1) + + top_index = view.proxy.index(0, 0, QModelIndex()) + assert view.proxy.rowCount(top_index) == 2 + + # filter for parent + view.proxy.setFilterRegularExpression(xid1[:5]) + + assert view.proxy.rowCount(QModelIndex()) == 1 + assert view.proxy.rowCount(top_index) == 0 + + # filter for child + view.proxy.setFilterRegularExpression(xid2) + + assert view.proxy.rowCount(top_index) == 1 + child_index = view.proxy.index(0, 0, top_index) + assert child_index.isValid() + assert child_index.data() == 'BP_BaugebietsTeilFlaeche' + + def test_filter_for_object_type(self, xplan_item, view, qtmodeltester): + item1 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_Plan) + item2 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_BaugebietsTeilFlaeche) + item3 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_Dachgestaltung) + + node1 = ClassNode(item1) + node2 = ClassNode(item2) + node3 = ClassNode(item3) + view.model.addChild(node1) + view.model.addChild(node2, node1) + view.model.addChild(node3, node1) + + top_index = view.proxy.index(0, 0, QModelIndex()) + assert view.proxy.rowCount(top_index) == 2 + + # filter for parent + view.proxy.setFilterRegularExpression('plan') + + assert view.proxy.rowCount(QModelIndex()) == 1 + assert view.proxy.rowCount(top_index) == 0 + + # filter for child + view.proxy.setFilterRegularExpression('baugebiet') + + assert view.proxy.rowCount(top_index) == 1 + child_index = view.proxy.index(0, 0, top_index) + assert child_index.isValid() + assert child_index.data() == 'BP_BaugebietsTeilFlaeche' + + def test_three_level_and_signals(self, view, qtmodeltester): + item1 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_Plan) + item2 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_BaugebietsTeilFlaeche) + item3 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_Dachgestaltung) + item4 = XPlanungItem(xid=str(uuid.uuid4()), xtype=XP_VerfahrensMerkmal) + + node1 = ClassNode(item1) + node2 = ClassNode(item2) + node3 = ClassNode(item3) + node4 = ClassNode(item4) + + spy = QSignalSpy(view.model.rowsInserted) + view.model.addChild(node1) + view.model.addChild(node2, node1) + view.model.addChild(node3, node2) + view.model.addChild(node4, node1) + assert len(spy) == 4 + + qtmodeltester.check(view.proxy) + qtmodeltester.check(view.proxy.sourceModel()) + + first_level_index = view.proxy.index(0, 0) + assert first_level_index.data() == 'BP_Plan' + assert view.proxy.rowCount(first_level_index) == 2 + second_level_index = view.proxy.index(0, 0, first_level_index) + assert second_level_index.data() == 'BP_BaugebietsTeilFlaeche' + assert view.proxy.rowCount(second_level_index) == 1 + second_level_index = view.proxy.index(1, 0, first_level_index) + assert second_level_index.data() == 'XP_VerfahrensMerkmal' + assert view.proxy.rowCount(second_level_index) == 0 + + first_level_index = view.proxy.sourceModel().index(0, 0) + assert first_level_index.data() == 'BP_Plan' + assert view.proxy.sourceModel().rowCount(first_level_index) == 2 + second_level_index = view.proxy.sourceModel().index(0, 0, view.proxy.sourceModel().index(0, 0)) + assert second_level_index.data() == 'BP_BaugebietsTeilFlaeche' + assert view.proxy.sourceModel().rowCount(second_level_index) == 1 + second_level_index = view.proxy.sourceModel().index(1, 0, first_level_index) + assert second_level_index.data() == 'XP_VerfahrensMerkmal' + assert view.proxy.sourceModel().rowCount(second_level_index) == 0 + + def test_new_nodes_flag(self, view): + item1 = XPlanungItem(xid=str(uuid.uuid4()), xtype=BP_Plan) + node1 = ClassNode(item1, new=True) + + view.model.addChild(node1) + + index = view.model.index(0, 0) + assert index.internalPointer().flag_new + + spy = QSignalSpy(view.model.dataChanged) + view.selectionChanged(QItemSelection(), QItemSelection(index, index)) + + assert len(spy) == 1 + assert not index.internalPointer().flag_new diff --git a/tests/QFeatureIdentify_test.py b/tests/QFeatureIdentify_test.py new file mode 100644 index 0000000..aee779a --- /dev/null +++ b/tests/QFeatureIdentify_test.py @@ -0,0 +1,68 @@ +import pytest +from qgis.PyQt.QtCore import Qt, QVariant +from qgis.core import QgsVectorLayer, QgsFeature, QgsGeometry, QgsField, QgsProject +from qgis.utils import iface + +from SAGisXPlanung.Tools.IdentifyFeatureTool import IdentifyFeatureTool +from SAGisXPlanung.gui.widgets.QFeatureIdentify import load_geometry, QFeatureIdentify + + +@pytest.fixture +def vector_feat(): + + def _vector_feat(wkt): + layer = QgsVectorLayer(f"Polygon?crs=4326", "test", "memory") + dp = layer.dataProvider() + layer.startEditing() + dp.addAttributes([QgsField("name", QVariant.String)]) + layer.updateFields() + feat1 = QgsFeature() + g = QgsGeometry.fromWkt(wkt) + feat1.setGeometry(g) + feat1.setAttributes(["test"]) + dp.addFeature(feat1) + layer.commitChanges() + + return layer, feat1 + + return _vector_feat + + +@pytest.fixture() +def widget(): + return QFeatureIdentify() + + +class TestQFeatureIdentify_loadLayer: + + def test_loadArea_with_polygon(self, vector_feat): + _, feat = vector_feat('POLYGON ((0 0, 1 1, 1 0, 0 0))') + geom = load_geometry(feat) + assert geom.wkt == 'MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)))' + + def test_loadArea_with_multipolygon(self, vector_feat): + _, feat = vector_feat('MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)))') + geom = load_geometry(feat) + assert geom.wkt == 'MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)))' + + +class TestQFeatureIdentify_createWidget: + + def test_createWidget(self, qtbot, widget): + qtbot.addWidget(widget) + assert widget.mapTool + assert widget.bIdentify + + +class TestQFeatureIdentify_testFeatureIdentify: + + def test_identifyFeature(self, vector_feat, qtbot, widget): + layer, _ = vector_feat('MULTIPOLYGON (((0 0, 1 1, 1 0, 0 0)))') + QgsProject.instance().addMapLayer(layer) + + qtbot.addWidget(widget) + qtbot.mouseClick(widget.bIdentify, Qt.LeftButton) + + assert widget.layer == layer + assert iface.mapCanvas().mapTool().__class__ == IdentifyFeatureTool + assert iface.mapCanvas().mapTool() == widget.mapTool diff --git a/tests/QXPlanTab_test.py b/tests/QXPlanTab_test.py new file mode 100644 index 0000000..4e4488b --- /dev/null +++ b/tests/QXPlanTab_test.py @@ -0,0 +1,154 @@ +from datetime import datetime + +import pytest +from qgis.PyQt import QtWidgets, QtCore + +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Plan +from SAGisXPlanung.gui.widgets.QXPlanInputElement import QComboBoxNoScroll, QDateEditNoScroll, QBooleanInput, QDateListInput +from SAGisXPlanung.gui.widgets.QXPlanScrollPage import QXPlanScrollPage +from SAGisXPlanung.gui.widgets.QXPlanTabWidget import QXPlanTabWidget + + +@pytest.fixture() +def scroll_page(mocker): + mocker.patch( + 'SAGisXPlanung.gui.widgets.QXPlanScrollPage.QAddRelationDropdown.refreshComboBox', + return_value=None + ) + page = QXPlanScrollPage(BP_Plan, None) + return page + + +@pytest.fixture() +def tab_widget(mocker): + mocker.patch( + 'SAGisXPlanung.gui.widgets.QXPlanScrollPage.QAddRelationDropdown.refreshComboBox', + return_value=None + ) + tab = QXPlanTabWidget(BP_Plan, None) + return tab + + +class TestQXPlanScrollPage_createInput: + + @pytest.mark.parametrize('input_name,expected_control,required', [('ausfertigungsDatum', QDateEditNoScroll, False), + ('rechtsstand', QComboBoxNoScroll, False), + ('gruenordnungsplan', QBooleanInput, False), + ('hoehenbezug', QtWidgets.QLineEdit, False), + ('auslegungsStartDatum', QDateListInput, False), + ('planArt', QComboBoxNoScroll, True)]) + def test_createInput(self, scroll_page, input_name, expected_control, required): + _, control = scroll_page.createInput(input_name, BP_Plan) + + assert isinstance(control, expected_control) + if required: + assert input_name in scroll_page.required_inputs + + +class TestQXPlanScrollPage_getObjectFromInputs: + + def test_get_object(self, mocker, scroll_page): + session_mock = mocker.MagicMock() + obj_mock = mocker.MagicMock() + session_mock.query.return_value.get.return_value = obj_mock + mocker.patch("SAGisXPlanung.Session.begin").return_value.__enter__.return_value = session_mock + + keys = BP_Plan.__table__.columns.keys() + for i, key in enumerate(keys): + if "id" in key or "srs" in key: + continue + label, control = scroll_page.createInput(key, BP_Plan) + + if key == 'name': + control.setText('test') + elif key == 'erstellungsMassstab': + control.setText('123') + elif key == 'auslegungsStartDatum': + control.setDefault('25.08.2021, 16.08.2021') + + if isinstance(control, QDateEditNoScroll): + control.setDate(QtCore.QDate(2020, 6, 10)) + + scroll_page.fields[key] = control + + assert scroll_page.fields + plan = scroll_page.getObjectFromInputs(validate_forms=False) + assert isinstance(plan, BP_Plan) + + # test causes strange segmentation fault ? + # def test_addRelation(self, scroll_page, qtbot): + # widget = scroll_page.fields['gemeinde'] + # assert widget + # qtbot.addWidget(widget) + # qtbot.mouseClick(widget.b_plus, QtCore.Qt.LeftButton, delay=1) + + +class TestQXPlanTabWidget_closeTab: + + def test_populateContent(self, tab_widget, mocker, qtbot): + mocker.patch( + 'SAGisXPlanung.gui.widgets.QXPlanScrollPage.QXPlanScrollPage.validateForms', + return_value=True + ) + session_mock = mocker.MagicMock() + obj_mock = mocker.MagicMock() + session_mock.query.return_value.get.return_value = obj_mock + mocker.patch("SAGisXPlanung.Session.begin").return_value.__enter__.return_value = session_mock + + qtbot.addWidget(tab_widget) + + # test adding tab + group_box = tab_widget.widget(0).vBox.itemAt(0).widget() + assert isinstance(group_box, QtWidgets.QGroupBox) + add_button = [button for button in group_box.findChildren(QtWidgets.QPushButton) + if button.text() == 'Hinzufügen'][0] + qtbot.mouseClick(add_button, QtCore.Qt.LeftButton) + assert tab_widget.count() == 2 + + # test populate content from multiple tabs + obj = tab_widget.populateContent() + assert obj + + def test_closeTab(self, tab_widget, qtbot): + qtbot.addWidget(tab_widget) + + # add tab + group_box = tab_widget.widget(0).vBox.itemAt(0).widget() + assert isinstance(group_box, QtWidgets.QGroupBox) + add_button = [button for button in group_box.findChildren(QtWidgets.QPushButton) + if button.text() == 'Hinzufügen'][0] + qtbot.mouseClick(add_button, QtCore.Qt.LeftButton) + + # test closing tab + close_button = tab_widget.tabBar().tabButton(1, QtWidgets.QTabBar.RightSide) + assert close_button + qtbot.mouseClick(close_button, QtCore.Qt.LeftButton) + + assert tab_widget.count() == 1 + + # test causes strange segmentation fault ? + # def test_closeTab_withDependencies(self, tab_widget, qtbot): + # qtbot.addWidget(tab_widget) + # + # # add first tab + # group_box = tab_widget.widget(0).vBox.itemAt(0).widget() + # assert isinstance(group_box, QtWidgets.QGroupBox) + # add_button = [button for button in group_box.findChildren(QtWidgets.QPushButton) + # if button.text() == 'Hinzufügen'][0] + # qtbot.mouseClick(add_button, QtCore.Qt.LeftButton) + # + # # add dependent subrelation + # group_box = tab_widget.widget(1).vBox.itemAt(1).widget() + # assert isinstance(group_box, QtWidgets.QGroupBox) + # add_button = [button for button in group_box.findChildren(QtWidgets.QPushButton) + # if button.text() == 'Hinzufügen'][0] + # qtbot.mouseClick(add_button, QtCore.Qt.LeftButton) + # + # # test closing tab + # close_button = tab_widget.tabBar().tabButton(1, QtWidgets.QTabBar.RightSide) + # assert close_button + # + # qtbot.mouseClick(close_button, QtCore.Qt.LeftButton) + # yes_button = tab_widget.close_warning.button(QtWidgets.QMessageBox.Yes) + # qtbot.mouseClick(yes_button, QtCore.Qt.LeftButton) + diff --git a/tests/XPPlanDetailsDialog_test.py b/tests/XPPlanDetailsDialog_test.py new file mode 100644 index 0000000..a1ab53c --- /dev/null +++ b/tests/XPPlanDetailsDialog_test.py @@ -0,0 +1,174 @@ +import asyncio +import os +import uuid + +import pytest + +from geoalchemy2 import WKBElement, WKTElement +from geoalchemy2.shape import from_shape +from qgis.PyQt import QtCore, QtTest +from qgis.core import (QgsSimpleLineSymbolLayer, QgsSingleSymbolRenderer, QgsSymbol, QgsWkbTypes, QgsProject, + QgsLayerTreeGroup) +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtCore import Qt +from qgis.utils import iface +from shapely import wkt + +from shapely.geometry import Polygon, Point +from shapely.strtree import STRtree + +from SAGisXPlanung.BPlan.BP_Bebauung.feature_types import BP_BaugebietsTeilFlaeche +from SAGisXPlanung.XPlan.enums import XP_AllgArtDerBaulNutzung +from SAGisXPlanung.XPlan.feature_types import XP_Plan +from SAGisXPlanung.gui.XPPlanDetailsDialog import createRasterLayer + +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Plan, BP_Bereich +from SAGisXPlanung.gui.XPPlanDetailsDialog import XPPlanDetailsDialog +from SAGisXPlanung.gui.widgets.QCustomTreeWidgetItems import GeometryIntersectionType, QGeometryPolygonTreeWidgetItem + + +@pytest.fixture() +def dialog(mocker): + mocker.patch( + 'SAGisXPlanung.gui.XPPlanDetailsDialog.XPPlanDetailsDialog.initPlanData', + return_value=None + ) + dlg = XPPlanDetailsDialog("test") + return dlg + + +@pytest.fixture() +def plan_correct(): + plan = BP_Plan() + plan.raeumlicherGeltungsbereich = WKTElement( + 'MultiPolygon (((571610.14549999998416752 5940812.23180000018328428, 571618.8227999999653548 5940833.81209999974817038, 571637.57389999995939434 5940849.39109999965876341, 571665.17790000000968575 5940876.14549999963492155, 571689.51969999994616956 5940870.4834000002592802, 571689.51969999994616956 5940870.4834000002592802, 571693.32970000000204891 5940859.52969999983906746, 571681.63450000004377216 5940846.81230000033974648, 571672.13399999996181577 5940838.12349999975413084, 571640.81519999995362014 5940809.48080000001937151, 571610.14549999998416752 5940812.23180000018328428)))', + srid=25832) + plan.name = 'correct' + + bereich = BP_Bereich() + bereich.name = 'correct' + bereich.geltungsbereich = plan.raeumlicherGeltungsbereich + + bp_objekt_poly = BP_BaugebietsTeilFlaeche() + bp_objekt_poly.flaechenschluss = True + bp_objekt_poly.position = WKTElement( + 'MultiPolygon (((571610.14549999998416752 5940812.23180000018328428, 571640.81519999995362014 5940809.48080000001937151, 571672.13399999996181577 5940838.12349999975413084, 571637.57389999995939434 5940849.39109999965876341, 571618.8227999999653548 5940833.81209999974817038, 571610.14549999998416752 5940812.23180000018328428)))', + srid=25832) + bereich.planinhalt.append(bp_objekt_poly) + + bp_objekt_poly2 = BP_BaugebietsTeilFlaeche() + bp_objekt_poly2.flaechenschluss = True + bp_objekt_poly2.position = WKTElement( + 'MultiPolygon (((571672.13399999996181577 5940838.12349999975413084, 571681.63450000004377216 5940846.81230000033974648, 571693.32970000000204891 5940859.52969999983906746, 571689.51969999994616956 5940870.4834000002592802, 571665.17790000000968575 5940876.14549999963492155, 571637.57389999995939434 5940849.39109999965876341, 571672.13399999996181577 5940838.12349999975413084)))', + srid=25832) + bereich.planinhalt.append(bp_objekt_poly2) + + plan.bereich.append(bereich) + return plan + + +@pytest.fixture() +def plan(): + plan = BP_Plan() + plan.raeumlicherGeltungsbereich = from_shape(Polygon([(0, 0), (0, 4), (4, 4), (4, 0)]), srid=25832) + plan.name = 'test_invalid' + + bereich = BP_Bereich() + bereich.name = 'test_invalid' + bereich.geltungsbereich = plan.raeumlicherGeltungsbereich + + # polygon + bp_objekt_poly = BP_BaugebietsTeilFlaeche() + bp_objekt_poly.flaechenschluss = True + bp_objekt_poly.position = from_shape(Polygon([(0, 3), (0, 4), (4, 4), (4, 3)]), srid=25832) + bereich.planinhalt.append(bp_objekt_poly) + + # intersecting polygon + bp_objekt_poly2 = BP_BaugebietsTeilFlaeche() + bp_objekt_poly2.flaechenschluss = True + bp_objekt_poly2.position = from_shape(Polygon([(3, 0), (3, 4), (4, 4), (4, 0)]), srid=25832) + bereich.planinhalt.append(bp_objekt_poly2) + + # intersecting with bounds + bp_objekt_poly3 = BP_BaugebietsTeilFlaeche() + bp_objekt_poly3.flaechenschluss = True + bp_objekt_poly3.position = from_shape(Polygon([(-1, 2), (-1, 3), (3, 3), (3, 2)]), srid=25832) + bereich.planinhalt.append(bp_objekt_poly3) + + # adjacent polygon with duplicate vertices + bp_objekt_poly4 = BP_BaugebietsTeilFlaeche() + bp_objekt_poly4.flaechenschluss = True + bp_objekt_poly4.position = from_shape(Polygon([(0, 1), (0, 2), (3, 2), (3, 2), (3, 0), (1, 0), (1, 1)]), srid=25832) + bereich.planinhalt.append(bp_objekt_poly4) + + plan.bereich.append(bereich) + return plan + + +class TestXPPlanDetailsDialog_constructExplorer: + + @pytest.mark.asyncio + async def test_constructExplorer(self, dialog): + plan = BP_Plan() + plan.id = uuid.uuid4() + plan.name = 'Test' + bereich = BP_Bereich() + bereich.name = 'Test' + plan.bereich.append(bereich) + + await dialog.constructExplorer(plan) + + model = dialog.objectTree.model + index_list = model.match(model.index(0, 0), Qt.DisplayRole, 'BP_Plan', -1, QtCore.Qt.MatchFixedString) + assert len(index_list) == 1 + item = model.itemAtIndex(index_list[0]) + assert item._data.xtype == BP_Plan + assert item.childCount() == 1 + assert item.data(0) == 'BP_Plan' + assert item.row() == 0 + + +class TestXPlanungDetailsDialog_GeometryValidation: + + def test_validateVertexUniqueness(self, dialog: XPPlanDetailsDialog, plan: XP_Plan): + spy = QtTest.QSignalSpy(dialog.log.model().rowsInserted) + + dialog.validateUniqueVertices(plan) + + assert len(spy) == 1 # 1 area with duplicate vertex + + def test_highlight_error(self, dialog: XPPlanDetailsDialog, plan): + item = QGeometryPolygonTreeWidgetItem(dialog.log, uid=str(plan.id), cls_type=plan.__class__, + polygon='MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))', + intersection_type=GeometryIntersectionType.Plan) + item.setSelected(True) + + dialog.highlightGeometryError() + + assert item.isVisible + + +class TestXPlanungDetailsDialog_displayOnMapCanvas: + pass # TODO: test map display + + +class TestXPlanungDialog_createRasterLayer: + + def test_createRasterLayer(self): + with open(os.path.join(os.path.dirname(__file__), 'data/bp_plan.tif'), 'rb') as file: + file_bytes = file.read() + + createRasterLayer("Raster", file_bytes) + + assert QgsProject.instance().mapLayersByName('Raster') + + def test_createRasterLayer_withGroup(self): + root = QgsProject.instance().layerTreeRoot() + layer_group = root.addGroup("Test_Raster") + + with open(os.path.join(os.path.dirname(__file__), 'data/bp_plan.tif'), 'rb') as file: + file_bytes = file.read() + + createRasterLayer("Raster", file_bytes, group=layer_group) + + assert QgsProject.instance().mapLayersByName('Raster') diff --git a/tests/XPlanungDialog_test.py b/tests/XPlanungDialog_test.py new file mode 100644 index 0000000..0099858 --- /dev/null +++ b/tests/XPlanungDialog_test.py @@ -0,0 +1,87 @@ +import pytest + + +from qgis.PyQt.QtCore import QModelIndex, Qt +from qgis.utils import iface + +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Plan, BP_Bereich +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui.XPlanungDialog import XPlanungDialog +from SAGisXPlanung.utils import is_url +from SAGisXPlanung.gui.widgets.QExplorerView import ClassNode, XID_ROLE + + +@pytest.fixture +def dialog(mocker): + mocker.patch( + 'SAGisXPlanung.gui.XPlanungDialog.QPlanComboBox.refresh', + return_value=None + ) + dialog = XPlanungDialog() + return dialog + + +class TestXPlanungDialog_is_url: + + def test_is_url_valid(self): + url = 'http://test.com' + assert is_url(url) + + def test_is_url_invalid(self): + url = 'smtp://test.com' + assert is_url(url) + + +class TestXPlanungDialog: + + def test_onIndexChanged(self, dialog, mocker): + mocker.patch( + 'SAGisXPlanung.gui.XPlanungDialog.XPPlanDetailsDialog.initPlanData', + return_value=None + ) + dialog.cbPlaene.addItems(['1', '2', '3']) + dialog.cbPlaene.setCurrentIndex(1) + + def test_onIdentifyClicked(self, dialog, qtbot): + qtbot.mouseClick(dialog.bIdentify, Qt.LeftButton) + + assert dialog.identifyTool is not None + assert iface.mapCanvas().mapTool() == dialog.identifyTool + + @pytest.mark.asyncio + async def test_onFeatureSaved(self, mocker, dialog): + session_mock = mocker.MagicMock() + obj_mock = mocker.MagicMock() + session_mock.query.return_value.get.return_value = obj_mock + mocker.patch("SAGisXPlanung.Session.begin").return_value.__enter__.return_value = session_mock + + bplan_item = XPlanungItem(xid='1', xtype=BP_Plan, plan_xid='1') + bpbereich_item = XPlanungItem(xid='2', xtype=BP_Bereich, plan_xid='1') + dialog.details_dialog.objectTree.model.addChild(ClassNode(bplan_item)) + dialog.details_dialog.plan_xid = '1' + + model = dialog.details_dialog.objectTree.model + await dialog.onFeatureSaved(bpbereich_item) + + index_list = model.match(model.index(0, 0), XID_ROLE, bpbereich_item.xid, -1, Qt.MatchWildcard | Qt.MatchRecursive) + assert index_list + assert index_list[0].parent().isValid() + assert index_list[0].internalPointer().flag_new + + @pytest.mark.asyncio + async def test_selectTreeItem(self, mocker, dialog): + session_mock = mocker.MagicMock() + obj_mock = mocker.MagicMock() + session_mock.query.return_value.get.return_value = obj_mock + mocker.patch("SAGisXPlanung.Session.begin").return_value.__enter__.return_value = session_mock + + bplan_item = XPlanungItem(xid='1', xtype=BP_Plan, plan_xid='1') + dialog.details_dialog.objectTree.model.addChild(ClassNode(bplan_item)) + dialog.details_dialog.plan_xid = '1' + + await dialog.selectTreeItem(bplan_item) + + index_list = dialog.details_dialog.objectTree.selectionModel().selectedIndexes() + assert index_list + assert index_list[0].data(Qt.DisplayRole) == 'BP_Plan' + diff --git a/tests/XPlanungPlugin_test.py b/tests/XPlanungPlugin_test.py new file mode 100644 index 0000000..5a8d0b6 --- /dev/null +++ b/tests/XPlanungPlugin_test.py @@ -0,0 +1,35 @@ +import pytest +from PyQt5.QtWidgets import QToolBar + +from qgis.PyQt.QtWidgets import QMenu +from qgis.core import QgsProject +from qgis.utils import iface, plugins, startPlugin + +from SAGisXPlanung import classFactory + + +def setup_module(module): + plugins['SAGisXPlanung'] = classFactory(iface) + assert plugins['SAGisXPlanung'] + + +class TestXPlanungPlugin_installation: + + def test_is_installed(self, mocker): + mocker.patch( + 'SAGisXPlanung.gui.XPlanungDialog.QPlanComboBox.refresh', + return_value=None + ) + assert startPlugin('SAGisXPlanung') + assert iface.mainWindow().findChild(QMenu, 'sagis_menu') + assert iface.mainWindow().findChild(QToolBar, 'sagis_toolbar') + + def test_unload(self): + plugins['SAGisXPlanung'].unload() + + with pytest.raises(TypeError): + QgsProject.instance().homePathChanged.disconnect(plugins['SAGisXPlanung'].onProjectLoaded) + + with pytest.raises(TypeError): + iface.layerTreeView().layerTreeModel().rowsInserted.disconnect(plugins['SAGisXPlanung'].onRowsInserted) + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..b9fa92b --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,7 @@ +import os +import sys +PROJECT_PATH = os.getcwd() +SOURCE_PATH = os.path.join( + PROJECT_PATH, "src" +) +sys.path.append(SOURCE_PATH) diff --git a/tests/context_menu_tool_actions_test.py b/tests/context_menu_tool_actions_test.py new file mode 100644 index 0000000..6d683e0 --- /dev/null +++ b/tests/context_menu_tool_actions_test.py @@ -0,0 +1,69 @@ +import uuid + +import pytest +from geoalchemy2 import WKTElement + +from qgis.core import QgsAnnotationLayer, QgsAnnotationItem, QgsPointXY + +from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry +from SAGisXPlanung.XPlan.XP_Praesentationsobjekte.feature_types import XP_PTO +from SAGisXPlanung.XPlanungItem import XPlanungItem +from SAGisXPlanung.gui.actions import MoveAnnotationItemAction + +xid = 'a8a1beff-0057-47e1-b855-4be26fdfa4fb' + + +@pytest.fixture() +def xplan_item(): + item = XPlanungItem(xid=xid, xtype=XP_PTO) + return item + + +@pytest.fixture() +def tpo() -> XP_PTO: + tpo = XP_PTO() + tpo.id = xid + tpo.position = WKTElement('POINT (1 1)', srid=25833) + tpo.schriftinhalt = 'test' + tpo.skalierung = 0.5 + tpo.drehwinkel = 0 + return tpo + + +@pytest.fixture() +def move_annotation_action(xplan_item) -> MoveAnnotationItemAction: + return MoveAnnotationItemAction(xplan_item, None) + + +class TestMoveAnnotationItemAction: + + def test_action_triggered(self, mocker, move_annotation_action, tpo): + mocker.patch( + 'SAGisXPlanung.gui.actions.MoveAnnotationItemAction.beginMove', + return_value=None + ) + + tpo.toCanvas(None) + + move_annotation_action.onActionTriggered(checked=False) + + assert move_annotation_action.annotation_layer + assert isinstance(move_annotation_action.annotation_layer, QgsAnnotationLayer) + assert move_annotation_action.annotation_item + assert isinstance(move_annotation_action.annotation_item, QgsAnnotationItem) + + def test_set_center(self, mocker, tpo, move_annotation_action): + point = QgsPointXY(10, 10) + move_annotation_action.setCenter(point) + + assert move_annotation_action.annotation_item is None + + item = tpo.asFeature() + move_annotation_action.annotation_item = item + layer_mock = mocker.MagicMock() + move_annotation_action.annotation_layer = layer_mock + + move_annotation_action.setCenter(point) + + assert move_annotation_action.annotation_item.point() == point + layer_mock.triggerRepaint.assert_called_once() diff --git a/tests/data/bp_plan.tif b/tests/data/bp_plan.tif new file mode 100644 index 0000000..4a03867 Binary files /dev/null and b/tests/data/bp_plan.tif differ diff --git a/tests/data/info.svg b/tests/data/info.svg new file mode 100644 index 0000000..3c231a9 --- /dev/null +++ b/tests/data/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/fp_test.py b/tests/fp_test.py new file mode 100644 index 0000000..cfd1f96 --- /dev/null +++ b/tests/fp_test.py @@ -0,0 +1,91 @@ +import pytest + +from SAGisXPlanung.FPlan.FP_Bebauung.feature_types import FP_BebauungsFlaeche +from SAGisXPlanung.XPlan.enums import XP_Sondernutzungen, XP_AllgArtDerBaulNutzung, XP_BesondereArtDerBaulNutzung +from SAGisXPlanung.XPlan.types import ConformityException + + +class TestFP_BebauungsFlaeche: + + def test_validation_sondernutzung_case1(self): + o = FP_BebauungsFlaeche() + + o.sonderNutzung = [XP_Sondernutzungen.Ferienhausgebiet.name] + o.allgArtDerBaulNutzung = XP_AllgArtDerBaulNutzung.SonderBauflaeche.name + o.besondereArtDerBaulNutzung = XP_BesondereArtDerBaulNutzung.AllgWohngebiet + + with pytest.raises(ConformityException) as exc_info: + o.validate() + + assert exc_info.value.code == '5.3.1.2' + + def test_validation_sondernutzung_case2(self): + o = FP_BebauungsFlaeche() + + o.sonderNutzung = [XP_Sondernutzungen.Einkaufszentrum.name] + o.allgArtDerBaulNutzung = XP_AllgArtDerBaulNutzung.SonderBauflaeche.name + o.besondereArtDerBaulNutzung = XP_BesondereArtDerBaulNutzung.AllgWohngebiet + + with pytest.raises(ConformityException) as exc_info: + o.validate() + + assert exc_info.value.code == '5.3.1.2' + + def test_validation_sondernutzung_valid(self): + o = FP_BebauungsFlaeche() + + o.sonderNutzung = [XP_Sondernutzungen.Einkaufszentrum.name] + o.allgArtDerBaulNutzung = XP_AllgArtDerBaulNutzung.SonderBauflaeche.name + o.besondereArtDerBaulNutzung = XP_BesondereArtDerBaulNutzung.SondergebietSonst.name + + o.validate() + + def test_validation_gfz_case1(self): + o = FP_BebauungsFlaeche() + o.GFZ = 0.5 + o.GFZmin = 0.2 + + with pytest.raises(ConformityException) as exc_info: + o.validate() + + assert exc_info.value.code == '5.3.1.4' + + def test_validation_gfz_case2(self): + o = FP_BebauungsFlaeche() + o.GFZmax = 0.9 + + with pytest.raises(ConformityException) as exc_info: + o.validate() + + assert exc_info.value.code == '5.3.1.4' + + def test_validation_gfz_valid_case1(self): + o = FP_BebauungsFlaeche() + o.GFZ = 0.5 + o.validate() + + def test_validation_gfz_valid_case2(self): + o = FP_BebauungsFlaeche() + o.GFZmin = 0.5 + o.GFZmax = 0.7 + o.validate() + + def test_validation_baunutzung_case1(self): + o = FP_BebauungsFlaeche() + o.allgArtDerBaulNutzung = XP_AllgArtDerBaulNutzung.WohnBauflaeche.name + o.besondereArtDerBaulNutzung = XP_BesondereArtDerBaulNutzung.Industriegebiet.name + + with pytest.raises(ConformityException) as exc_info: + o.validate() + + assert exc_info.value.code == '5.3.1.1' + + def test_validation_baunutzung_case2(self): + o = FP_BebauungsFlaeche() + o.allgArtDerBaulNutzung = XP_AllgArtDerBaulNutzung.WohnBauflaeche.name + o.besondereArtDerBaulNutzung = XP_BesondereArtDerBaulNutzung.Dorfgebiet.name + + with pytest.raises(ConformityException) as exc_info: + o.validate() + + assert exc_info.value.code == '5.3.1.1' diff --git a/tests/geometry_test.py b/tests/geometry_test.py new file mode 100644 index 0000000..67b6f54 --- /dev/null +++ b/tests/geometry_test.py @@ -0,0 +1,45 @@ +from qgis.core import QgsGeometry, QgsWkbTypes + +from SAGisXPlanung.GML.geometry import enforce_wkb_constraints, geometry_from_spatial_element, geom_type_as_layer_url +from geoalchemy2 import WKBElement, WKTElement + + +class TestGeometry_enforce_wkb_constraints: + + def test_enforce_wkb_constraints_using_ewkb(self): + ewkb = '0103000020E6100000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000' + wkb = '0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000' + + assert len(ewkb) == len(wkb) + 8 # wkb should be 4 bytes less -> 8 digits in hex + assert enforce_wkb_constraints(ewkb) == wkb + + def test_enforce_wkb_constraints_using_wkb(self): + wkb = '0106000000010000000103000000010000000400000000000000000000000000000000000000000000000000f03f000000000000f03f000000000000f03f000000000000000000000000000000000000000000000000' + + assert enforce_wkb_constraints(wkb) == wkb + + +class TestGeometry_geometry_from_spatial_element: + + def test_enforce_wkb_constraints_using_wkt_element(self): + wkt_element = WKTElement('POINT (1 1)', srid=25833) + geom = geometry_from_spatial_element(wkt_element) + assert isinstance(geom, QgsGeometry) + assert not geom.isEmpty() and not geom.isNull() + assert geom.asPoint().x() == 1 + assert geom.asPoint().y() == 1 + + def test_enforce_wkb_constraints_using_wkb_element(self): + wkb = '0106000000010000000103000000010000000400000000000000000000000000000000000000000000000000f03f000000000000f03f000000000000f03f000000000000000000000000000000000000000000000000' + wkb_element = WKBElement(bytes.fromhex(wkb), srid=25833) + geom = geometry_from_spatial_element(wkb_element) + assert isinstance(geom, QgsGeometry) + assert not geom.isEmpty() and not geom.isNull() + + +class TestGeometry_geom_type_as_layer_url: + + def test_geom_type_as_layer_url(self): + assert geom_type_as_layer_url(QgsWkbTypes.PointGeometry).lower() == 'point' + assert geom_type_as_layer_url(QgsWkbTypes.LineGeometry).lower() == 'linestring' + assert geom_type_as_layer_url(QgsWkbTypes.PolygonGeometry).lower() == 'polygon' diff --git a/tests/gml/bp_plan.gml b/tests/gml/bp_plan.gml new file mode 100644 index 0000000..abbcdef --- /dev/null +++ b/tests/gml/bp_plan.gml @@ -0,0 +1,310 @@ + + + + + 3478519.018 5889194.887 + 3480922.418 5890728.997 + + + + + + + 3478519.018 5889194.887 + 3480922.418 5890728.997 + + + bp_plan + test_kommentar + 2021-06-04 + + + + + 3479367.656 5889625.167 3479364.766 5889617.858 3479361.087 5889608.551 3479392.706 5889596.055 3479457.758 5889511.227 3479573.376 5889465.541 3479574.249 5889465.196 3479578.025 5889463.704 3480237.382 5889203.16 3480258.319 5889194.887 3480266.396 5889215.351 3480286.381 5889267.29 3480312.598 5889332.402 3480314.433 5889337.052 3480603.948 5889222.652 3480607.59 5889228.202 3480340.743 5889333.644 3480321.469 5889341.26 3480366.984 5889456.572 3480377.069 5889452.587 3480405.002 5889523.156 3480415.073 5889521.44 3480465.316 5889512.879 3480489.609 5889503.281 3480490.182 5889503.055 3480498.444 5889499.79 3480503.548 5889497.908 3480531.106 5889488.451 3480532.277 5889488.105 3480533.474 5889487.863 3480534.688 5889487.726 3480535.909 5889487.695 3480537.128 5889487.771 3480538.336 5889487.953 3480539.523 5889488.239 3480540.681 5889488.628 3480541.332 5889488.898 3480542.426 5889489.442 3480543.468 5889490.079 3480544.45 5889490.805 3480545.365 5889491.613 3480546.207 5889492.498 3480546.968 5889493.453 3480547.643 5889494.471 3480548.227 5889495.544 3480548.521 5889496.185 3480549.881 5889499.628 3480574.277 5889489.997 3480572.947 5889486.63 3480572.548 5889485.475 3480572.252 5889484.291 3480572.059 5889483.084 3480571.973 5889481.866 3480571.993 5889480.645 3480572.12 5889479.43 3480572.352 5889478.231 3480572.687 5889477.056 3480572.801 5889476.728 3480573.266 5889475.598 3480573.827 5889474.514 3480574.481 5889473.482 3480575.222 5889472.511 3480576.046 5889471.609 3480576.944 5889470.781 3480577.911 5889470.036 3480578.94 5889469.377 3480579.243 5889469.206 3480579.486 5889469.076 3480687.901 5889426.55 3480690.272 5889425.596 3480704.322 5889419.094 3480717.752 5889411.392 3480730.46 5889402.549 3480742.349 5889392.633 3480753.328 5889381.718 3480763.314 5889369.887 3480763.454 5889369.72 3480763.608 5889369.566 3480763.775 5889369.426 3480763.953 5889369.301 3480764.142 5889369.193 3480764.339 5889369.101 3480764.544 5889369.026 3480764.754 5889368.97 3480764.969 5889368.932 3480765.186 5889368.913 3480765.403 5889368.913 3480765.62 5889368.932 3480765.835 5889368.97 3480766.045 5889369.027 3480766.25 5889369.101 3480766.447 5889369.193 3480766.636 5889369.302 3480766.814 5889369.427 3480766.823 5889369.433 3480768.75 5889370.921 3480768.77 5889370.936 3480774.151 5889363.261 3480781.666 5889350.72 3480786.272 5889343.052 3480791.378 5889336.004 3480791.486 5889335.858 3480791.626 5889335.637 3480791.746 5889335.405 3480791.846 5889335.163 3480791.924 5889334.913 3480791.981 5889334.658 3480792.014 5889334.399 3480792.025 5889334.137 3480792.013 5889333.876 3480791.979 5889333.617 3480791.922 5889333.361 3480791.843 5889333.112 3480791.742 5889332.871 3480791.621 5889332.639 3480791.48 5889332.418 3480791.321 5889332.211 3480791.144 5889332.018 3480790.951 5889331.842 3480790.743 5889331.683 3480790.522 5889331.543 3480776.045 5889323.561 3480789.389 5889325.3 3480807.971 5889326.581 3480838.403 5889326.203 3480851.806 5889324.664 3480859.563 5889324.378 3480853.214 5889325.971 3480850.489 5889326.705 3480843.843 5889328.941 3480837.417 5889331.748 3480831.26 5889335.105 3480825.42 5889338.985 3480819.939 5889343.359 3480817.657 5889345.427 3480812.768 5889350.452 3480812.487 5889350.74 3480812.182 5889351.003 3480811.855 5889351.238 3480811.509 5889351.444 3480811.146 5889351.619 3480811.056 5889351.657 3480806.569 5889356.339 3480797.079 5889367.576 3480784.874 5889383.377 3480785.037 5889383.522 3480785.186 5889383.681 3480785.321 5889383.852 3480785.441 5889384.034 3480785.544 5889384.227 3480785.63 5889384.427 3480785.699 5889384.634 3480785.744 5889384.822 3480785.777 5889385.037 3480785.792 5889385.255 3480785.788 5889385.473 3480785.764 5889385.69 3480785.722 5889385.904 3480785.661 5889386.113 3480785.582 5889386.317 3480785.498 5889386.491 3480776.541 5889401.63 3480766.299 5889415.93 3480754.849 5889429.283 3480742.279 5889441.588 3480728.684 5889452.75 3480714.168 5889462.685 3480698.842 5889471.317 3480690.202 5889475.443 3480599.917 5889515.958 3480598.78 5889516.404 3480597.608 5889516.749 3480596.411 5889516.99 3480595.197 5889517.127 3480593.976 5889517.157 3480592.757 5889517.08 3480591.55 5889516.898 3480590.362 5889516.611 3480589.205 5889516.221 3480588.086 5889515.733 3480587.013 5889515.148 3480586.553 5889514.858 3480559.705 5889525.623 3480563.349 5889534.846 3480563.71 5889536.013 3480563.969 5889537.206 3480564.122 5889538.418 3480564.169 5889539.638 3480564.109 5889540.858 3480563.944 5889542.068 3480563.673 5889543.259 3480563.3 5889544.422 3480562.827 5889545.548 3480562.258 5889546.628 3480561.596 5889547.655 3480560.848 5889548.62 3480560.018 5889549.516 3480559.114 5889550.337 3480558.141 5889551.075 3480557.108 5889551.726 3480556.022 5889552.284 3480555.313 5889552.587 3480515.974 5889568.13 3480515.399 5889568.357 3480490.839 5889578.061 3480448.018 5889604.655 3480439.382 5889610.018 3480466.32 5889678.068 3480513.676 5889797.699 3480498.61 5889803.664 3480505.218 5889820.404 3480566.545 5889976.011 3480561.519 5889990.68 3480553.559 5890009.123 3480552.471 5890011.338 3480560.477 5890010.889 3480566.736 5890011.684 3480573.957 5890010.458 3480584.437 5890007.877 3480594.653 5890004.393 3480604.526 5890000.031 3480607.245 5889998.643 3480616.572 5889993.205 3480625.389 5889986.974 3480633.63 5889979.999 3480641.232 5889972.331 3480648.136 5889964.031 3480693.838 5889903.933 3480702.555 5889893.453 3480712.152 5889883.772 3480722.557 5889874.965 3480733.689 5889867.098 3480745.465 5889860.231 3480757.794 5889854.417 3480761.069 5889853.087 3480871.876 5889809.548 3480877.34 5889807.671 3480882.947 5889806.278 3480888.653 5889805.379 3480891.663 5889805.108 3480912.327 5889803.72 3480912.218 5889813.094 3480913.04 5889826.022 3480913.372 5889828.32 3480914.176 5889832.016 3480915.298 5889835.628 3480916.731 5889839.129 3480918.464 5889842.491 3480920.483 5889845.69 3480922.418 5889852.129 3480912.166 5889852.36 3480872.254 5889857.245 3480861.348 5889859.562 3480835.307 5889868.066 3480804.206 5889880.544 3480801.004 5889881.587 3480795.512 5889883.802 3480790.234 5889886.487 3480785.21 5889889.622 3480776.181 5889895.165 3480773.658 5889896.528 3480773.233 5889896.753 3480762.91 5889902.824 3480753.156 5889909.771 3480738.079 5889921.715 3480728.665 5889929.619 3480714.555 5889942.0 3480714.081 5889942.406 3480704.614 5889951.284 3480695.958 5889960.954 3480673.876 5889986.698 3480661.95 5889996.493 3480654.713 5890001.615 3480628.112 5890023.901 3480616.923 5890031.346 3480613.017 5890021.479 3480601.242 5890026.499 3480589.074 5890030.474 3480576.607 5890033.373 3480563.934 5890035.174 3480551.152 5890035.865 3480539.112 5890035.494 3480538.893 5890036.351 3480538.581 5890038.25 3480538.503 5890040.066 3480539.193 5890041.198 3480541.451 5890042.751 3480571.397 5890061.061 3480571.932 5890060.712 3480610.966 5890084.774 3480605.713 5890090.796 3480534.942 5890047.83 3480516.552 5890036.666 3480515.871 5890038.128 3480515.065 5890039.526 3480514.14 5890040.848 3480513.104 5890042.084 3480511.963 5890043.225 3480510.728 5890044.263 3480509.407 5890045.189 3480509.141 5890045.355 3480500.618 5890050.59 3480473.856 5890067.026 3480456.659 5890077.588 3480451.435 5890080.796 3480410.555 5890096.929 3480224.796 5890170.246 3480195.304 5890181.886 3480172.052 5890191.064 3480076.3 5890228.856 3479956.498 5890276.141 3479930.205 5890286.518 3479906.952 5890295.696 3479800.272 5890337.802 3479793.238 5890340.578 3479778.82 5890346.269 3479665.107 5890391.152 3479659.404 5890393.403 3479641.854 5890400.33 3479528.615 5890445.023 3479525.08 5890446.418 3479521.677 5890447.761 3479511.126 5890451.921 3479506.813 5890453.622 3479440.825 5890479.672 3479300.343 5890535.122 3479268.389 5890547.733 3479259.725 5890551.152 3479245.135 5890556.91 3479161.11 5890590.071 3478970.145 5890665.449 3478940.044 5890677.328 3478930.982 5890680.904 3478897.13 5890694.264 3478809.119 5890728.997 3478717.844 5890723.6 3478703.136 5890718.101 3478688.963 5890711.342 3478675.434 5890703.372 3478662.65 5890694.254 3478650.71 5890684.057 3478639.704 5890672.857 3478631.196 5890662.678 3478616.814 5890626.287 3478606.118 5890599.223 3478589.337 5890556.764 3478578.678 5890529.795 3478530.761 5890408.55 3478528.008 5890401.584 3478519.018 5890363.689 3478537.084 5890356.559 3478566.462 5890344.954 3478622.118 5890322.975 3478738.369 5890277.067 3478752.723 5890271.399 3478775.974 5890262.217 3478873.148 5890223.843 3478990.72 5890177.413 3479109.891 5890130.352 3479243.842 5890077.454 3479275.279 5890065.046 3479282.771 5890062.087 3479333.624 5890042.0 3479342.76 5890038.392 3479354.774 5890033.647 3479310.819 5889924.592 3479316.229 5889922.452 3479434.441 5889875.696 3479462.342 5889864.66 3479445.646 5889822.429 3479422.492 5889763.864 3479420.654 5889759.215 3479416.132 5889747.778 3479414.402 5889743.404 3479412.564 5889738.754 3479383.141 5889664.334 3479372.858 5889638.326 3479367.656 5889625.167 + + + + + + + gses + 2021-08-19 + asdfgr + true + + + + + Dokument + ref1 + D:\Downloads\document.pdf + application/pdf + 2021-08-19 + 1000 + + + + + 37815 + Berlin + + + + + 4326436 + Bestensee + + + + + Plangeber1 + 213 + + + 1000 + 1000 + 1000 + 1000 + 2021-08-19 + 2021-08-20 + false + false + false + false + false + + + + + + + + + 3478519.018 5889194.887 + 3480922.418 5890728.997 + + + 3262 + planbereich1 + 1600 + + + + + 3479367.656 5889625.167 3479364.766 5889617.858 3479361.087 5889608.551 3479392.706 5889596.055 3479457.758 5889511.227 3479573.376 5889465.541 3479574.249 5889465.196 3479578.025 5889463.704 3480237.382 5889203.16 3480258.319 5889194.887 3480266.396 5889215.351 3480286.381 5889267.29 3480312.598 5889332.402 3480314.433 5889337.052 3480603.948 5889222.652 3480607.59 5889228.202 3480340.743 5889333.644 3480321.469 5889341.26 3480366.984 5889456.572 3480377.069 5889452.587 3480405.002 5889523.156 3480415.073 5889521.44 3480465.316 5889512.879 3480489.609 5889503.281 3480490.182 5889503.055 3480498.444 5889499.79 3480503.548 5889497.908 3480531.106 5889488.451 3480532.277 5889488.105 3480533.474 5889487.863 3480534.688 5889487.726 3480535.909 5889487.695 3480537.128 5889487.771 3480538.336 5889487.953 3480539.523 5889488.239 3480540.681 5889488.628 3480541.332 5889488.898 3480542.426 5889489.442 3480543.468 5889490.079 3480544.45 5889490.805 3480545.365 5889491.613 3480546.207 5889492.498 3480546.968 5889493.453 3480547.643 5889494.471 3480548.227 5889495.544 3480548.521 5889496.185 3480549.881 5889499.628 3480574.277 5889489.997 3480572.947 5889486.63 3480572.548 5889485.475 3480572.252 5889484.291 3480572.059 5889483.084 3480571.973 5889481.866 3480571.993 5889480.645 3480572.12 5889479.43 3480572.352 5889478.231 3480572.687 5889477.056 3480572.801 5889476.728 3480573.266 5889475.598 3480573.827 5889474.514 3480574.481 5889473.482 3480575.222 5889472.511 3480576.046 5889471.609 3480576.944 5889470.781 3480577.911 5889470.036 3480578.94 5889469.377 3480579.243 5889469.206 3480579.486 5889469.076 3480687.901 5889426.55 3480690.272 5889425.596 3480704.322 5889419.094 3480717.752 5889411.392 3480730.46 5889402.549 3480742.349 5889392.633 3480753.328 5889381.718 3480763.314 5889369.887 3480763.454 5889369.72 3480763.608 5889369.566 3480763.775 5889369.426 3480763.953 5889369.301 3480764.142 5889369.193 3480764.339 5889369.101 3480764.544 5889369.026 3480764.754 5889368.97 3480764.969 5889368.932 3480765.186 5889368.913 3480765.403 5889368.913 3480765.62 5889368.932 3480765.835 5889368.97 3480766.045 5889369.027 3480766.25 5889369.101 3480766.447 5889369.193 3480766.636 5889369.302 3480766.814 5889369.427 3480766.823 5889369.433 3480768.75 5889370.921 3480768.77 5889370.936 3480774.151 5889363.261 3480781.666 5889350.72 3480786.272 5889343.052 3480791.378 5889336.004 3480791.486 5889335.858 3480791.626 5889335.637 3480791.746 5889335.405 3480791.846 5889335.163 3480791.924 5889334.913 3480791.981 5889334.658 3480792.014 5889334.399 3480792.025 5889334.137 3480792.013 5889333.876 3480791.979 5889333.617 3480791.922 5889333.361 3480791.843 5889333.112 3480791.742 5889332.871 3480791.621 5889332.639 3480791.48 5889332.418 3480791.321 5889332.211 3480791.144 5889332.018 3480790.951 5889331.842 3480790.743 5889331.683 3480790.522 5889331.543 3480776.045 5889323.561 3480789.389 5889325.3 3480807.971 5889326.581 3480838.403 5889326.203 3480851.806 5889324.664 3480859.563 5889324.378 3480853.214 5889325.971 3480850.489 5889326.705 3480843.843 5889328.941 3480837.417 5889331.748 3480831.26 5889335.105 3480825.42 5889338.985 3480819.939 5889343.359 3480817.657 5889345.427 3480812.768 5889350.452 3480812.487 5889350.74 3480812.182 5889351.003 3480811.855 5889351.238 3480811.509 5889351.444 3480811.146 5889351.619 3480811.056 5889351.657 3480806.569 5889356.339 3480797.079 5889367.576 3480784.874 5889383.377 3480785.037 5889383.522 3480785.186 5889383.681 3480785.321 5889383.852 3480785.441 5889384.034 3480785.544 5889384.227 3480785.63 5889384.427 3480785.699 5889384.634 3480785.744 5889384.822 3480785.777 5889385.037 3480785.792 5889385.255 3480785.788 5889385.473 3480785.764 5889385.69 3480785.722 5889385.904 3480785.661 5889386.113 3480785.582 5889386.317 3480785.498 5889386.491 3480776.541 5889401.63 3480766.299 5889415.93 3480754.849 5889429.283 3480742.279 5889441.588 3480728.684 5889452.75 3480714.168 5889462.685 3480698.842 5889471.317 3480690.202 5889475.443 3480599.917 5889515.958 3480598.78 5889516.404 3480597.608 5889516.749 3480596.411 5889516.99 3480595.197 5889517.127 3480593.976 5889517.157 3480592.757 5889517.08 3480591.55 5889516.898 3480590.362 5889516.611 3480589.205 5889516.221 3480588.086 5889515.733 3480587.013 5889515.148 3480586.553 5889514.858 3480559.705 5889525.623 3480563.349 5889534.846 3480563.71 5889536.013 3480563.969 5889537.206 3480564.122 5889538.418 3480564.169 5889539.638 3480564.109 5889540.858 3480563.944 5889542.068 3480563.673 5889543.259 3480563.3 5889544.422 3480562.827 5889545.548 3480562.258 5889546.628 3480561.596 5889547.655 3480560.848 5889548.62 3480560.018 5889549.516 3480559.114 5889550.337 3480558.141 5889551.075 3480557.108 5889551.726 3480556.022 5889552.284 3480555.313 5889552.587 3480515.974 5889568.13 3480515.399 5889568.357 3480490.839 5889578.061 3480448.018 5889604.655 3480439.382 5889610.018 3480466.32 5889678.068 3480513.676 5889797.699 3480498.61 5889803.664 3480505.218 5889820.404 3480566.545 5889976.011 3480561.519 5889990.68 3480553.559 5890009.123 3480552.471 5890011.338 3480560.477 5890010.889 3480566.736 5890011.684 3480573.957 5890010.458 3480584.437 5890007.877 3480594.653 5890004.393 3480604.526 5890000.031 3480607.245 5889998.643 3480616.572 5889993.205 3480625.389 5889986.974 3480633.63 5889979.999 3480641.232 5889972.331 3480648.136 5889964.031 3480693.838 5889903.933 3480702.555 5889893.453 3480712.152 5889883.772 3480722.557 5889874.965 3480733.689 5889867.098 3480745.465 5889860.231 3480757.794 5889854.417 3480761.069 5889853.087 3480871.876 5889809.548 3480877.34 5889807.671 3480882.947 5889806.278 3480888.653 5889805.379 3480891.663 5889805.108 3480912.327 5889803.72 3480912.218 5889813.094 3480913.04 5889826.022 3480913.372 5889828.32 3480914.176 5889832.016 3480915.298 5889835.628 3480916.731 5889839.129 3480918.464 5889842.491 3480920.483 5889845.69 3480922.418 5889852.129 3480912.166 5889852.36 3480872.254 5889857.245 3480861.348 5889859.562 3480835.307 5889868.066 3480804.206 5889880.544 3480801.004 5889881.587 3480795.512 5889883.802 3480790.234 5889886.487 3480785.21 5889889.622 3480776.181 5889895.165 3480773.658 5889896.528 3480773.233 5889896.753 3480762.91 5889902.824 3480753.156 5889909.771 3480738.079 5889921.715 3480728.665 5889929.619 3480714.555 5889942.0 3480714.081 5889942.406 3480704.614 5889951.284 3480695.958 5889960.954 3480673.876 5889986.698 3480661.95 5889996.493 3480654.713 5890001.615 3480628.112 5890023.901 3480616.923 5890031.346 3480613.017 5890021.479 3480601.242 5890026.499 3480589.074 5890030.474 3480576.607 5890033.373 3480563.934 5890035.174 3480551.152 5890035.865 3480539.112 5890035.494 3480538.893 5890036.351 3480538.581 5890038.25 3480538.503 5890040.066 3480539.193 5890041.198 3480541.451 5890042.751 3480571.397 5890061.061 3480571.932 5890060.712 3480610.966 5890084.774 3480605.713 5890090.796 3480534.942 5890047.83 3480516.552 5890036.666 3480515.871 5890038.128 3480515.065 5890039.526 3480514.14 5890040.848 3480513.104 5890042.084 3480511.963 5890043.225 3480510.728 5890044.263 3480509.407 5890045.189 3480509.141 5890045.355 3480500.618 5890050.59 3480473.856 5890067.026 3480456.659 5890077.588 3480451.435 5890080.796 3480410.555 5890096.929 3480224.796 5890170.246 3480195.304 5890181.886 3480172.052 5890191.064 3480076.3 5890228.856 3479956.498 5890276.141 3479930.205 5890286.518 3479906.952 5890295.696 3479800.272 5890337.802 3479793.238 5890340.578 3479778.82 5890346.269 3479665.107 5890391.152 3479659.404 5890393.403 3479641.854 5890400.33 3479528.615 5890445.023 3479525.08 5890446.418 3479521.677 5890447.761 3479511.126 5890451.921 3479506.813 5890453.622 3479440.825 5890479.672 3479300.343 5890535.122 3479268.389 5890547.733 3479259.725 5890551.152 3479245.135 5890556.91 3479161.11 5890590.071 3478970.145 5890665.449 3478940.044 5890677.328 3478930.982 5890680.904 3478897.13 5890694.264 3478809.119 5890728.997 3478717.844 5890723.6 3478703.136 5890718.101 3478688.963 5890711.342 3478675.434 5890703.372 3478662.65 5890694.254 3478650.71 5890684.057 3478639.704 5890672.857 3478631.196 5890662.678 3478616.814 5890626.287 3478606.118 5890599.223 3478589.337 5890556.764 3478578.678 5890529.795 3478530.761 5890408.55 3478528.008 5890401.584 3478519.018 5890363.689 3478537.084 5890356.559 3478566.462 5890344.954 3478622.118 5890322.975 3478738.369 5890277.067 3478752.723 5890271.399 3478775.974 5890262.217 3478873.148 5890223.843 3478990.72 5890177.413 3479109.891 5890130.352 3479243.842 5890077.454 3479275.279 5890065.046 3479282.771 5890062.087 3479333.624 5890042.0 3479342.76 5890038.392 3479354.774 5890033.647 3479310.819 5889924.592 3479316.229 5889922.452 3479434.441 5889875.696 3479462.342 5889864.66 3479445.646 5889822.429 3479422.492 5889763.864 3479420.654 5889759.215 3479416.132 5889747.778 3479414.402 5889743.404 3479412.564 5889738.754 3479383.141 5889664.334 3479372.858 5889638.326 3479367.656 5889625.167 + + + + + + + Dokument + ref1 + https://www.geoportal-hoppegarten.de/docs/hopp_bp02/Plandokument.pdf + application/pdf + + + + + PlanMitGeoreferenz + ref2 + https://www.geoportal-hoppegarten.de/docs/hopp_bp02/Plandokument.pdf + application/pdf + + + + + + + + + + 3478519.018 5889194.887 + 3480922.418 5890728.997 + + + 325325 + planbereich2 + 1600 + + + + + 3479367.656 5889625.167 3479364.766 5889617.858 3479361.087 5889608.551 3479392.706 5889596.055 3479457.758 5889511.227 3479573.376 5889465.541 3479574.249 5889465.196 3479578.025 5889463.704 3480237.382 5889203.16 3480258.319 5889194.887 3480266.396 5889215.351 3480286.381 5889267.29 3480312.598 5889332.402 3480314.433 5889337.052 3480603.948 5889222.652 3480607.59 5889228.202 3480340.743 5889333.644 3480321.469 5889341.26 3480366.984 5889456.572 3480377.069 5889452.587 3480405.002 5889523.156 3480415.073 5889521.44 3480465.316 5889512.879 3480489.609 5889503.281 3480490.182 5889503.055 3480498.444 5889499.79 3480503.548 5889497.908 3480531.106 5889488.451 3480532.277 5889488.105 3480533.474 5889487.863 3480534.688 5889487.726 3480535.909 5889487.695 3480537.128 5889487.771 3480538.336 5889487.953 3480539.523 5889488.239 3480540.681 5889488.628 3480541.332 5889488.898 3480542.426 5889489.442 3480543.468 5889490.079 3480544.45 5889490.805 3480545.365 5889491.613 3480546.207 5889492.498 3480546.968 5889493.453 3480547.643 5889494.471 3480548.227 5889495.544 3480548.521 5889496.185 3480549.881 5889499.628 3480574.277 5889489.997 3480572.947 5889486.63 3480572.548 5889485.475 3480572.252 5889484.291 3480572.059 5889483.084 3480571.973 5889481.866 3480571.993 5889480.645 3480572.12 5889479.43 3480572.352 5889478.231 3480572.687 5889477.056 3480572.801 5889476.728 3480573.266 5889475.598 3480573.827 5889474.514 3480574.481 5889473.482 3480575.222 5889472.511 3480576.046 5889471.609 3480576.944 5889470.781 3480577.911 5889470.036 3480578.94 5889469.377 3480579.243 5889469.206 3480579.486 5889469.076 3480687.901 5889426.55 3480690.272 5889425.596 3480704.322 5889419.094 3480717.752 5889411.392 3480730.46 5889402.549 3480742.349 5889392.633 3480753.328 5889381.718 3480763.314 5889369.887 3480763.454 5889369.72 3480763.608 5889369.566 3480763.775 5889369.426 3480763.953 5889369.301 3480764.142 5889369.193 3480764.339 5889369.101 3480764.544 5889369.026 3480764.754 5889368.97 3480764.969 5889368.932 3480765.186 5889368.913 3480765.403 5889368.913 3480765.62 5889368.932 3480765.835 5889368.97 3480766.045 5889369.027 3480766.25 5889369.101 3480766.447 5889369.193 3480766.636 5889369.302 3480766.814 5889369.427 3480766.823 5889369.433 3480768.75 5889370.921 3480768.77 5889370.936 3480774.151 5889363.261 3480781.666 5889350.72 3480786.272 5889343.052 3480791.378 5889336.004 3480791.486 5889335.858 3480791.626 5889335.637 3480791.746 5889335.405 3480791.846 5889335.163 3480791.924 5889334.913 3480791.981 5889334.658 3480792.014 5889334.399 3480792.025 5889334.137 3480792.013 5889333.876 3480791.979 5889333.617 3480791.922 5889333.361 3480791.843 5889333.112 3480791.742 5889332.871 3480791.621 5889332.639 3480791.48 5889332.418 3480791.321 5889332.211 3480791.144 5889332.018 3480790.951 5889331.842 3480790.743 5889331.683 3480790.522 5889331.543 3480776.045 5889323.561 3480789.389 5889325.3 3480807.971 5889326.581 3480838.403 5889326.203 3480851.806 5889324.664 3480859.563 5889324.378 3480853.214 5889325.971 3480850.489 5889326.705 3480843.843 5889328.941 3480837.417 5889331.748 3480831.26 5889335.105 3480825.42 5889338.985 3480819.939 5889343.359 3480817.657 5889345.427 3480812.768 5889350.452 3480812.487 5889350.74 3480812.182 5889351.003 3480811.855 5889351.238 3480811.509 5889351.444 3480811.146 5889351.619 3480811.056 5889351.657 3480806.569 5889356.339 3480797.079 5889367.576 3480784.874 5889383.377 3480785.037 5889383.522 3480785.186 5889383.681 3480785.321 5889383.852 3480785.441 5889384.034 3480785.544 5889384.227 3480785.63 5889384.427 3480785.699 5889384.634 3480785.744 5889384.822 3480785.777 5889385.037 3480785.792 5889385.255 3480785.788 5889385.473 3480785.764 5889385.69 3480785.722 5889385.904 3480785.661 5889386.113 3480785.582 5889386.317 3480785.498 5889386.491 3480776.541 5889401.63 3480766.299 5889415.93 3480754.849 5889429.283 3480742.279 5889441.588 3480728.684 5889452.75 3480714.168 5889462.685 3480698.842 5889471.317 3480690.202 5889475.443 3480599.917 5889515.958 3480598.78 5889516.404 3480597.608 5889516.749 3480596.411 5889516.99 3480595.197 5889517.127 3480593.976 5889517.157 3480592.757 5889517.08 3480591.55 5889516.898 3480590.362 5889516.611 3480589.205 5889516.221 3480588.086 5889515.733 3480587.013 5889515.148 3480586.553 5889514.858 3480559.705 5889525.623 3480563.349 5889534.846 3480563.71 5889536.013 3480563.969 5889537.206 3480564.122 5889538.418 3480564.169 5889539.638 3480564.109 5889540.858 3480563.944 5889542.068 3480563.673 5889543.259 3480563.3 5889544.422 3480562.827 5889545.548 3480562.258 5889546.628 3480561.596 5889547.655 3480560.848 5889548.62 3480560.018 5889549.516 3480559.114 5889550.337 3480558.141 5889551.075 3480557.108 5889551.726 3480556.022 5889552.284 3480555.313 5889552.587 3480515.974 5889568.13 3480515.399 5889568.357 3480490.839 5889578.061 3480448.018 5889604.655 3480439.382 5889610.018 3480466.32 5889678.068 3480513.676 5889797.699 3480498.61 5889803.664 3480505.218 5889820.404 3480566.545 5889976.011 3480561.519 5889990.68 3480553.559 5890009.123 3480552.471 5890011.338 3480560.477 5890010.889 3480566.736 5890011.684 3480573.957 5890010.458 3480584.437 5890007.877 3480594.653 5890004.393 3480604.526 5890000.031 3480607.245 5889998.643 3480616.572 5889993.205 3480625.389 5889986.974 3480633.63 5889979.999 3480641.232 5889972.331 3480648.136 5889964.031 3480693.838 5889903.933 3480702.555 5889893.453 3480712.152 5889883.772 3480722.557 5889874.965 3480733.689 5889867.098 3480745.465 5889860.231 3480757.794 5889854.417 3480761.069 5889853.087 3480871.876 5889809.548 3480877.34 5889807.671 3480882.947 5889806.278 3480888.653 5889805.379 3480891.663 5889805.108 3480912.327 5889803.72 3480912.218 5889813.094 3480913.04 5889826.022 3480913.372 5889828.32 3480914.176 5889832.016 3480915.298 5889835.628 3480916.731 5889839.129 3480918.464 5889842.491 3480920.483 5889845.69 3480922.418 5889852.129 3480912.166 5889852.36 3480872.254 5889857.245 3480861.348 5889859.562 3480835.307 5889868.066 3480804.206 5889880.544 3480801.004 5889881.587 3480795.512 5889883.802 3480790.234 5889886.487 3480785.21 5889889.622 3480776.181 5889895.165 3480773.658 5889896.528 3480773.233 5889896.753 3480762.91 5889902.824 3480753.156 5889909.771 3480738.079 5889921.715 3480728.665 5889929.619 3480714.555 5889942.0 3480714.081 5889942.406 3480704.614 5889951.284 3480695.958 5889960.954 3480673.876 5889986.698 3480661.95 5889996.493 3480654.713 5890001.615 3480628.112 5890023.901 3480616.923 5890031.346 3480613.017 5890021.479 3480601.242 5890026.499 3480589.074 5890030.474 3480576.607 5890033.373 3480563.934 5890035.174 3480551.152 5890035.865 3480539.112 5890035.494 3480538.893 5890036.351 3480538.581 5890038.25 3480538.503 5890040.066 3480539.193 5890041.198 3480541.451 5890042.751 3480571.397 5890061.061 3480571.932 5890060.712 3480610.966 5890084.774 3480605.713 5890090.796 3480534.942 5890047.83 3480516.552 5890036.666 3480515.871 5890038.128 3480515.065 5890039.526 3480514.14 5890040.848 3480513.104 5890042.084 3480511.963 5890043.225 3480510.728 5890044.263 3480509.407 5890045.189 3480509.141 5890045.355 3480500.618 5890050.59 3480473.856 5890067.026 3480456.659 5890077.588 3480451.435 5890080.796 3480410.555 5890096.929 3480224.796 5890170.246 3480195.304 5890181.886 3480172.052 5890191.064 3480076.3 5890228.856 3479956.498 5890276.141 3479930.205 5890286.518 3479906.952 5890295.696 3479800.272 5890337.802 3479793.238 5890340.578 3479778.82 5890346.269 3479665.107 5890391.152 3479659.404 5890393.403 3479641.854 5890400.33 3479528.615 5890445.023 3479525.08 5890446.418 3479521.677 5890447.761 3479511.126 5890451.921 3479506.813 5890453.622 3479440.825 5890479.672 3479300.343 5890535.122 3479268.389 5890547.733 3479259.725 5890551.152 3479245.135 5890556.91 3479161.11 5890590.071 3478970.145 5890665.449 3478940.044 5890677.328 3478930.982 5890680.904 3478897.13 5890694.264 3478809.119 5890728.997 3478717.844 5890723.6 3478703.136 5890718.101 3478688.963 5890711.342 3478675.434 5890703.372 3478662.65 5890694.254 3478650.71 5890684.057 3478639.704 5890672.857 3478631.196 5890662.678 3478616.814 5890626.287 3478606.118 5890599.223 3478589.337 5890556.764 3478578.678 5890529.795 3478530.761 5890408.55 3478528.008 5890401.584 3478519.018 5890363.689 3478537.084 5890356.559 3478566.462 5890344.954 3478622.118 5890322.975 3478738.369 5890277.067 3478752.723 5890271.399 3478775.974 5890262.217 3478873.148 5890223.843 3478990.72 5890177.413 3479109.891 5890130.352 3479243.842 5890077.454 3479275.279 5890065.046 3479282.771 5890062.087 3479333.624 5890042.0 3479342.76 5890038.392 3479354.774 5890033.647 3479310.819 5889924.592 3479316.229 5889922.452 3479434.441 5889875.696 3479462.342 5889864.66 3479445.646 5889822.429 3479422.492 5889763.864 3479420.654 5889759.215 3479416.132 5889747.778 3479414.402 5889743.404 3479412.564 5889738.754 3479383.141 5889664.334 3479372.858 5889638.326 3479367.656 5889625.167 + + + + + + + PlanMitGeoreferenz + ref1 + https://www.geoportal-hoppegarten.de/docs/hopp_bp08/Plandokument.pdf + application/pdf + + + + + + + + + + + + + 567531.092 5929495.862 + 567576.591 5929520.177 + + + (F) + + + 2500 + 3500 + 5.0 + + + 1000 + + + + + + 567575.525 5929520.177 567531.092 5929518.067 567531.804 5929503.09 +567537.295 5929503.351 567537.651 5929495.862 567576.591 5929497.711 +567575.525 5929520.177 + + + + + true + + + 20 + + + 20 + 2100 + 0.4 + 2 + 1200 + 1000 + + + + + + + 571791.4822 5940866.2293 + 571809.2106 5940919.7494 + + + 1000 + + 1000 + + + + + + 571809.2106 5940866.4215 571796.8083 5940919.7494 571791.4822 5940919.3918 +571804.0319 5940866.2293 571809.2106 5940866.4215 + + + + + false + 4000 + 1000 + + + + + + + 564984.128 5940442.486 + 565038.252 5940474.619 + + + 0 + + + + + 1000 + + + + + 565029.337 5940457.156 565028.611 5940456.469 565020.516 5940465.024 565016.424 5940469.350 565011.438 5940474.619 564999.828 5940463.629 564988.215 5940452.640 564984.128 5940448.773 564984.128 5940442.486 565034.802 5940442.486 565038.252 5940447.733 565029.337 5940457.156 + + + + + true + 1400 + + + + + + + 565010.917 5940455.804 + 565011.917 5940455.804 + + + besondereArtDerBaulNutzung[0] + 0 + + + + + 565010.917 5940455.804 + + + 4.20 + 1.0 + + + + + + + 567639.111 5929650.213 + 567639.111 5929650.213 + + + gliederung1 + 0 + + + (B) + + + 567639.111 5929650.213 + + + 0 + + + + + + 407320.25472985354 5788344.084623076 + 407320.25472985354 5788344.084623076 + + + + 0.5 + + + 407320.25472985354 5788344.084623076 + + + 0 + 3 + 3 + + diff --git a/tests/gml/bp_plan1.gml b/tests/gml/bp_plan1.gml new file mode 100644 index 0000000..d0853e9 --- /dev/null +++ b/tests/gml/bp_plan1.gml @@ -0,0 +1,55 @@ + + + + + 363245.85320911836 5822955.651306625 + 363915.46399956784 5823699.63869305 + + + + + + + 363245.85320911836 5822955.651306625 + 363915.46399956784 5823699.63869305 + + + Denkmalbereichssatzung Eisenbahnersiedlung + 1. Satzung zur Änderung der Satzung zum Schutz des Denkmalbereiches Eisenbahner-Siedlung Elstal + + + + + + + 363557.43937451066 5823582.309326799 363555.02258413506 5823579.638944966 363532.7829256258 5823554.057290428 363545.8023034477 5823543.2896246705 363591.7756474285 5823513.227667849 363631.2606457714 5823513.633189429 363665.779783048 5823513.931994822 363670.8256812115 5823520.398997272 363684.309274554 5823509.796741642 363744.9934482636 5823587.147319698 363751.6374217355 5823595.624144101 363790.9641242798 5823563.7142182635 363852.11725641653 5823514.3354414655 363867.5650209003 5823532.828175185 363882.88946887275 5823520.230919284 363887.8600728577 5823516.3606780125 363915.46399956784 5823497.322505879 363891.39830813714 5823462.371760877 363860.6403245092 5823417.683754438 363844.9459270035 5823394.794312805 363844.6328927831 5823394.448077985 363829.2135859559 5823372.017120818 363778.2933527669 5823298.033538095 363749.9684987613 5823256.905900667 363735.1325738904 5823234.920780116 363730.38778669736 5823236.545237995 363708.23613624455 5823144.953744235 363696.12055257626 5823095.47325786 363679.1513575298 5823026.221375614 363672.1395315518 5822997.607026551 363664.9386905233 5822968.185323238 363611.199392919 5822998.132614869 363592.3175623068 5822964.129580226 363581.70180252375 5822970.000991978 363577.2778666225 5822969.382652779 363573.47226991976 5822964.256058698 363571.7043819389 5822965.2229163535 363566.34450534213 5822955.651306625 363552.1901589647 5822963.535131409 363323.63442478934 5823090.909492982 363284.2680002563 5823112.804321871 363245.85320911836 5823134.136087335 363265.16787734255 5823168.583436476 363269.7929296707 5823176.82140105 363260.8563667115 5823193.302326877 363265.58947225433 5823201.692377912 363277.7963625583 5823223.68526685 363300.6592984766 5823270.901398626 363337.68969718495 5823351.750186413 363346.6060392029 5823371.108341373 363354.7226521072 5823388.805152136 363373.18496992666 5823429.226214912 363373.21994668973 5823429.214555991 363434.9839135542 5823400.974983726 363442.44390554616 5823417.37752446 363388.6179972501 5823441.911225892 363384.1476337304 5823443.93654707 363351.42104177846 5823458.85163847 363337.3112487243 5823465.284031879 363302.5243588313 5823481.146827023 363290.3310009578 5823486.704176754 363290.94913197914 5823488.041829773 363294.4622147861 5823495.64386282 363305.67400252755 5823519.752013625 363305.6820874346 5823519.944860042 363350.0642688474 5823602.771500919 363385.7812810301 5823659.054603129 363390.1971128092 5823665.959645403 363401.322561136 5823683.472948726 363403.2266048285 5823686.46478826 363405.16966927395 5823689.517314557 363406.7455770838 5823691.971102814 363411.8269384667 5823699.63869305 363411.97569499986 5823699.54379496 363413.34706256585 5823698.666657815 363437.6768466552 5823683.095117688 363455.1371853276 5823672.027475669 363467.5919219059 5823664.085475503 363475.0514543997 5823659.400132122 363479.1295539872 5823655.303316116 363504.7871639749 5823629.5609623445 363506.17906761094 5823628.164899508 363526.0323571657 5823610.42580708 363536.0874066015 5823601.43750687 363541.2115461666 5823596.903281096 363547.6402894421 5823591.2322061835 363548.5664055749 5823590.411456556 363550.0359910552 5823588.991781527 363557.43937451066 5823582.309326799 + + + + + + + + + Dokument + Satzung Denkmalbereichssatzung "Eisenbahnersiedlung" + https://www.wustermark.de/fileadmin/user_upload/Ortsrecht/E%2027_Denkmalbereichssatzung%20Eisenbahnersiedlung%20Elstal.pdf + application/pdf + 1060 + + + + + 12063357 + Wustermark + Elstal + + + 1000 + 1000 + 1000 + 1000 + 2001-03-14 + + + diff --git a/tests/renderer_test.py b/tests/renderer_test.py new file mode 100644 index 0000000..a4e086a --- /dev/null +++ b/tests/renderer_test.py @@ -0,0 +1,98 @@ +import pytest + +from qgis.core import (QgsSimpleLineSymbolLayer, QgsRuleBasedRenderer, QgsSimpleFillSymbolLayer, QgsWkbTypes, + QgsSingleSymbolRenderer) +from qgis.PyQt.QtGui import QColor + +from SAGisXPlanung.BPlan.BP_Basisobjekte.feature_types import BP_Plan +from SAGisXPlanung.BPlan.BP_Bebauung.feature_types import BP_BaugebietsTeilFlaeche, BP_BauGrenze +from SAGisXPlanung.BPlan.BP_Gemeinbedarf_Spiel_und_Sportanlagen.feature_types import BP_SpielSportanlagenFlaeche, \ + BP_GemeinbedarfsFlaeche +from SAGisXPlanung.BPlan.BP_Landwirtschaft_Wald_und_Gruenflaechen.feature_types import BP_GruenFlaeche, \ + BP_LandwirtschaftsFlaeche, BP_WaldFlaeche +from SAGisXPlanung.BPlan.BP_Naturschutz_Landschaftsbild_Naturhaushalt.feature_types import BP_AnpflanzungBindungErhaltung +from SAGisXPlanung.BPlan.BP_Ver_und_Entsorgung.feature_types import BP_VerEntsorgung +from SAGisXPlanung.BPlan.BP_Verkehr.feature_types import BP_StrassenVerkehrsFlaeche, \ + BP_VerkehrsflaecheBesondererZweckbestimmung +from SAGisXPlanung.BPlan.BP_Wasser.feature_types import BP_GewaesserFlaeche +from SAGisXPlanung.XPlan.enums import XP_AllgArtDerBaulNutzung + + +class TestRenderer: + + @pytest.mark.parametrize('cls_type, expected_color, expected_renderer', + [(BP_Plan, QColor(0, 0, 0), QgsSimpleLineSymbolLayer), + (BP_BaugebietsTeilFlaeche, QColor('#f4c3b4'), QgsRuleBasedRenderer), + (BP_BauGrenze, QColor('#1e8ebe'), QgsSimpleLineSymbolLayer), + (BP_LandwirtschaftsFlaeche, QColor('#befe9d'), QgsSimpleFillSymbolLayer), + (BP_WaldFlaeche, QColor('#17a8a5'), QgsSimpleFillSymbolLayer), + (BP_StrassenVerkehrsFlaeche, QColor('#fbdd19'), QgsSimpleFillSymbolLayer), + ]) + def test_renderer(self, cls_type, expected_color, expected_renderer): + c = cls_type() + if cls_type == BP_BaugebietsTeilFlaeche: + c.allgArtDerBaulNutzung = XP_AllgArtDerBaulNutzung.WohnBauflaeche + r = c.renderer() + + if isinstance(r, QgsRuleBasedRenderer): + assert isinstance(r, expected_renderer) + assert any(rule.symbol().color() == expected_color for rule in r.rootRule().children()) + return + + symbol = r.symbol() + assert symbol.symbolLayerCount() + assert isinstance(symbol.symbolLayer(0), expected_renderer) + assert symbol.symbolLayer(0).color() == expected_color + + def test_spiel_sportanlage_renderer(self): + r = BP_SpielSportanlagenFlaeche.renderer() + + assert isinstance(r, QgsRuleBasedRenderer) + assert len(r.rootRule().children()) == 3 + assert any(rule.isElse() for rule in r.rootRule().children()) + + def test_gruenflaeche_renderer(self): + r = BP_GruenFlaeche.renderer() + + assert isinstance(r, QgsRuleBasedRenderer) + assert len(r.rootRule().children()) == 8 + assert any(rule.isElse() for rule in r.rootRule().children()) + + def test_gewaesser_renderer(self): + r = BP_GewaesserFlaeche.renderer() + + assert isinstance(r, QgsRuleBasedRenderer) + assert len(r.rootRule().children()) == 2 + assert any(rule.isElse() for rule in r.rootRule().children()) + + def test_gemeinbedarf_renderer(self): + r = BP_GemeinbedarfsFlaeche.renderer() + + assert isinstance(r, QgsRuleBasedRenderer) + assert len(r.rootRule().children()) == 12 + assert any(rule.isElse() for rule in r.rootRule().children()) + + def test_besonderer_verkehr_renderer(self): + r = BP_VerkehrsflaecheBesondererZweckbestimmung.renderer() + + assert isinstance(r, QgsRuleBasedRenderer) + assert len(r.rootRule().children()) == 4 + assert any(rule.isElse() for rule in r.rootRule().children()) + + def test_ver_entsorgung_renderer(self): + r = BP_VerEntsorgung.renderer() + + assert isinstance(r, QgsRuleBasedRenderer) + assert len(r.rootRule().children()) == 10 + assert any(rule.isElse() for rule in r.rootRule().children()) + + def test_pflanzung_renderer(self): + r = BP_AnpflanzungBindungErhaltung.renderer(QgsWkbTypes.PointGeometry) + + assert isinstance(r, QgsRuleBasedRenderer) + assert len(r.rootRule().children()) == 6 + + r = BP_AnpflanzungBindungErhaltung.renderer(QgsWkbTypes.PolygonGeometry) + + assert isinstance(r, QgsSingleSymbolRenderer) + diff --git a/tests/run_tests.py b/tests/run_tests.py new file mode 100644 index 0000000..45eb78d --- /dev/null +++ b/tests/run_tests.py @@ -0,0 +1,15 @@ +import nest_asyncio +import pytest + + +def run_all(): + nest_asyncio.apply() + exit_code = pytest.main(["--cov=SAGisXPlanung", "--cache-clear", "--cov-report=term-missing", "--asyncio-mode=auto"]) + if int(exit_code) == 0: + print('Ran OK') + else: + pytest.exit('tests failed', returncode=1) + + +if __name__ == '__main__': + run_all() diff --git a/tests/xsd/XPlanGML.xsd b/tests/xsd/XPlanGML.xsd new file mode 100644 index 0000000..e613031 --- /dev/null +++ b/tests/xsd/XPlanGML.xsd @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tests/xsd/XPlanGML_BPlan.xsd b/tests/xsd/XPlanGML_BPlan.xsd new file mode 100644 index 0000000..22996c4 --- /dev/null +++ b/tests/xsd/XPlanGML_BPlan.xsd @@ -0,0 +1,3780 @@ + + + + + + + + + + + + Diese Klasse modelliert einen Bereich eines Bebauungsplans, z.B. einen räumlichen oder sachlichen Teilbereich. + + + + + + + + + + + + + + xplan:BP_Plan + xplan:bereich + + + + + + + + + + + + + + + + Basisklasse für alle Objekte eines Bebauungsplans mit flächenhaftem Raumbezug. Die von BP_Flaechenobjekt abgeleiteten Fachobjekte können sowohl als Flächenschlussobjekte als auch als Überlagerungsobjekte auftreten. + + + + + + + + + + + + + + + + + + + + Basisklasse für alle Objekte eines Bebauungsplans mit flächenhaftem Raumbezug, die auf Ebene 0 immer Flächenschlussobjekte sind. +Flächenschlussobjekte dürfen sich nicht überlappen, sondern nur an den Flächenrändern berühren, wobei die jeweiligen Stützpunkte der Randkurven übereinander liegen müssen. Die Vereinigung der Flächenschlussobjekte überdeckt den Geltungsbereich des Bebauungsplans vollständig. + + + + + + + + + + + + + + + + + Basisklasse für alle Objekte eines Bebauungsplans mit variablem Raumbezug. Das bedeutet, die abgeleiteten Objekte können kontextabhängig mit Punkt-, Linien- oder Flächengeometrie gebildet. Die Aggregation von Punkten, Linien oder Flächen ist zulässig, nicht aber die Mischung von Punkt-, Linien- und Flächengeometrie. + + + + + + + + + + + + + + + + + + + + + Basisklasse für alle Objekte eines Bebauungsplans mit linienförmigem Raumbezug (Eine einzelne zusammenhängende Kurve, die aus Linienstücken und Kreisbögen zusammengesetzt sein kann, oder eine Menge derartiger Kurven). + + + + + + + + + + + + + + + + + + Basisklasse für alle raumbezogenen Festsetzungen, Hinweise, Vermerke und Kennzeichnungen eines Bebauungsplans. + + + + + + + + + xplan:XP_TextAbschnitt + + + + + + + xplan:BP_AusgleichsFlaeche + + + + + + + xplan:BP_AnpflanzungBindungErhaltung + + + + + + + xplan:BP_SchutzPflegeEntwicklungsMassnahme + + + + + + + xplan:BP_SchutzPflegeEntwicklungsFlaeche + + + + + + + xplan:BP_AusgleichsMassnahme + + + + + + + + + xplan:BP_ZusatzkontingentLaerm + + + + + + + xplan:BP_ZusatzkontingentLaermFlaeche + + + + + + + xplan:BP_RichtungssektorGrenze + + + + + + + + + + + + + + + + Die Klasse modelliert einen Bebauungsplan + + + + + + + + + + + + xplan:BP_SonstPlanArt + + + + + + + + + xplan:BP_Status + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xplan:BP_Bereich + xplan:gehoertZuPlan + + + + + + + + + + + + + + + + + + Planwerk der verbindlichen Bauleitplanung auf kommunaler Ebene + + + + + + Einfacher BPlan, §30 Abs. 3 BauGB. + + + + + + Qualifizierter BPlan nach §30 Abs. 1 BauGB. + + + + + + Vorhabensbezogener Bebauungsplan nach §12 BauGB + + + + + + Satzung über Vorhaben- und Erschließungsplan gemäß §7 Maßnahmengesetz (BauGB-MaßnahmenG) von 1993 + + + + + + Kommunale Satzung gemäß §34 BauGB + + + + + + Klarstellungssatzung nach § 34 Abs.4 Nr.1 BauGB. + + + + + + + Entwicklungssatzung nach § 34 Abs.4 Nr. 2 BauGB. + + + + + + + Ergänzungssatzung nach § 34 Abs.4 Nr. 3 BauGB. + + + + + + + Außenbereichssatzung nach § 35 Abs. 6 BauGB. + + + + + + + Örtliche Bauvorschrift. + + + + + + + Sonstige Planart. + + + + + + + + + + + + Basisklasse für alle Objekte eines Bebauungsplans mit punktförmigem Raumbezug (Einzelpunkt oder Punktmenge). + + + + + + + + + + + + + + + + + + + + + Festsetzung in Bebauungsplan. + + + + + + Nachrichtliche Übernahme aus anderen Planwerken. + + + + + + Hinweis nach BauGB + + + + + + Vermerk nach § 5 BauGB + + + + + + Kennzeichnung von Flächen nach $9 Absatz 5 BauGB. Kennzeichnungen sind keine rechtsverbindlichen Festsetzungen, sondern Hinweise auf Besonderheiten (insbesondere der Baugrundverhältnisse), deren Kenntnis für das Verständnis des Bebauungsplans und seiner Festsetzungen wie auch für die Vorbereitung und Genehmigung von Vorhaben notwendig sind. + + + + + + Der Rechtscharakter des BPlan-Inhaltes ist unbekannt. + + + + + + + + + + Aufzählung der möglichen Rechtsstände eines BPlans + + + + + + Ein Aufstellungsbeschluss der Gemeinde liegt vor. + + + + + + Ein Planentwurf liegt vor. + + + + + + Die frühzeitige Beteiligung der Behörden (§ 4 Abs. 1 BauGB) hat stattgefunden. + + + + + + Die frühzeitige Beteiligung der Öffentlichkeit (§ 3 Abs. 1 BauGB), bzw. bei einem Verfahren nach § 13a BauGB die Unterrichtung der Öffentlichkeit (§ 13a Abs. 3 BauGB) hat stattgefunden. + + + + + + Die Beteiligung der Behörden hat stattgefunden (§ 4 Abs. 2 BauGB). + + + + + + Der Plan hat öffentlich ausgelegen. (§ 3 Abs. 2 BauGB). + + + + + + Die Satzung wurde durch Beschluss der Gemeinde verabschiedet. + + + + + + Der Plan ist in kraft getreten. + + + + + + Der Plan ist, z. B. durch einen Gerichtsbeschluss oder neuen Plan, teilweise untergegangen. + + + + + + Der Plan wurde außer Kraft gesetzt. + + + + + + Der Plan wurde durch ein förmliches Verfahren aufgehoben + + + + + + Der Plan ist ohne förmliches Verfahren z.B. durch Überplanung außer Kraft getreten + + + + + + + + + + + Texlich formulierter Inhalt eines Bebauungsplans, der einen anderen Rechtscharakter als das zugrunde liegende Fachobjekt hat (Attribut <i>rechtscharakter </i>des Fachobjektes), oder dem Plan als Ganzes zugeordnet ist. + + + + + + + + + + + + + + + + + + + Basisklasse für alle Objekte eines Bebauungsplans mit flächenhaftem Raumbezug, die immer Überlagerungsobjekte sind. + + + + + + + + + + + + + + + + + + + Normales BPlan Verfahren. + + + + + + BPlan Verfahren nach Paragraph 13 BauGB. + + + + + + BPlan Verfahren nach Paragraph 13a BauGB. + + + + + + BPlan Verfahren nach Paragraph 13b BauGB. + + + + + + + + + + + Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen (§9, Abs. 1, Nr. 17 BauGB)). Hier: Flächen für Abgrabungen und die Gewinnung von Bodenschätzen. + + + + + + + + + + + + + + + + + + Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen (§ 9 Abs. 1 Nr. 17 und Abs. 6 BauGB). Hier: Flächen für Aufschüttungen + + + + + + + + + + + + + + + + + + Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen (§ 9 Abs. 1 Nr. 17 und Abs. 6 BauGB). Hier: Flächen für Gewinnung von Bodenschätzen + +Die Klasse wird als <b>veraltet </b>gekennzeichnet und wird in XPlanGML V. 6.0 wegfallen. Es sollte stattdessen die Klasse <i>BP_AbgrabungsFlaeche </i>verwendet werden. + + + + + + + + + + + + + + + + + + Rekultivierungs-Fläche + +Die Klasse wird als <b>veraltet </b>gekennzeichnet und wird in XPlanGML 6.0 wegfallen. Es sollte stattdessen die Klasse SO_SonstigesRecht verwendet werden. + + + + + + + + + + + + + + + + + Festsetzung eines vom Bauordnungsrecht abweichenden Maßes der Tiefe der Abstandsfläche gemäß § 9 Abs 1. Nr. 2a BauGB + + + + + + + + + + + + + + + + + + + Linienhafte Festlegung des Umfangs der Abweichung von der Baugrenze (§23 Abs. 3 Satz 3 BauNVO). + + + + + + + + + + + + + + + + + Flächenhafte Festlegung des Umfangs der Abweichung von der überbaubaren Grundstücksfläche (§23 Abs. 3 Satz 3 BauNVO). + + + + + + + + + + + + + + + + + Teil eines Baugebiets mit einheitlicher Art der baulichen Nutzung. Das Maß der baulichen Nutzung sowie Festsetzungen zur Bauweise oder Grenzbebauung können innerhalb einer <i>BP_BaugebietsTeilFlaeche </i>unterschiedlich sein (<i>BP_UeberbaubareGrundstueckeFlaeche</i>). Dabei sollte die gleichzeitige Belegung <b>desselben Attributs </b>in <i>BP_BaugebietsTeilFlaeche </i>und einem überlagernden Objekt <i>BP_UeberbaubareGrunsdstuecksFlaeche </i><b>verzichtet werden</b>. Ab Version 6.0 wird dies evtl. durch eine Konformitätsregel erzwungen. + + + + + + + + + + + + + + + xplan:BP_DetailDachform + + + + + + + xplan:BP_TextAbschnitt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xplan:BP_DetailArtDerBaulNutzung + + + + + + + xplan:BP_DetailSondernutzung + + + + + + + + + + xplan:BP_AbweichendeBauweise + + + + + + + + + + + + + + + + + + + + + + + Festsetzung einer Baugrenze (§9 Abs. 1 Nr. 2 BauGB, §22 und 23 BauNVO). Über die Attribute <i>geschossMin </i>und <i>geschossMax </i>kann die Festsetzung auf einen Bereich von Geschossen beschränkt werden. Wenn eine Einschränkung der Festsetzung durch expliziter Höhenangaben erfolgen soll, ist dazu die Oberklassen-Relation <i>hoehenangabe </i>auf den komplexen Datentyp <i>XP_Hoehenangabe </i>zu verwenden. +Durch die Digitalisierungsreihenfolge der Linienstützpunkte muss sichergestellt sein, dass die überbaute Fläche (<i>BP_UeberbaubareGrundstuecksFlaeche</i>) relativ zur Laufrichtung auf der linken Seite liegt. + + + + + + + + + + + + + + + + + + + + Festsetzung einer Baulinie (§9 Abs. 1 Nr. 2 BauGB, §22 und 23 BauNVO). Über die Attribute <i>geschossMin </i>und <i>geschossMax </i>kann die Festsetzung auf einen Bereich von Geschossen beschränkt werden. Wenn eine Einschränkung der Festsetzung durch explizite Höhenangaben erfolgen soll, ist dazu die Oberklassen-Relation <i>hoehenangabe </i>auf den komplexen Datentyp <i>XP_Hoehenangabe </i>zu verwenden. +Durch die Digitalisierungsreihenfolge der Linienstützpunkte muss sichergestellt sein, dass die überbaute Fläche (<i>BP_UeberbaubareGrundstuecksFlaeche</i>) relativ zur Laufrichtung auf der linken Seite liegt. + + + + + + + + + + + + + + + + + + + Aufzählung verschiedener Bauweisen. + + + + + + + Offene Bauweise + + + + + + Geschlossene Bauweise + + + + + + Abweichende Bauweise + + + + + + + + + + Aufzählung verschiedener Brbauungs-Arten eines Baugebiets. + + + + + + Nur Einzelhäuser zulässig. + + + + + + Nur Doppelhäuser zulässig. + + + + + + Nur Hausgruppen zulässig. + + + + + + Nur Einzel- oder Doppelhäuser zulässig. + + + + + + Nur Einzelhäuser oder Hausgruppen zulässig. + + + + + + Nur Doppelhäuser oder Hausgruppen zulässig. + + + + + + Nur Reihenhäuser zulässig. + + + + + + Es sind Einzelhäuser, Doppelhäuser und Hausgruppen zulässig. + + + + + + + + + + + Festsetzung einer Fläche mit besonderem Nutzungszweck, der durch besondere städtebauliche Gründe erfordert wird (§9 Abs. 1 Nr. 9 BauGB.) + + + + + + + + + + + + + + + + xplan:BP_DetailDachform + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xplan:BP_AbweichendeBauweise + + + + + + + + + + + + + + + + Aufzählung verschiedener Dachformen. + + + + + + Flachdach +Empfohlene Abkürzung: <b>FD</b> + + + + + + Pultdach +Empfohlene Abkürzung: <b>PD</b> + + + + + + Versetztes Pultdach +Empfohlene Abkürzung: <b>VPD</b> + + + + + + Kein Flachdach +Empfohlene Abkürzung: <b>GD</b> + + + + + + Satteldach +Empfohlene Abkürzung: <b>SD</b> + + + + + + Walmdach +Empfohlene Abkürzung: <b>WD</b> + + + + + + Krüppelwalmdach +Empfohlene Abkürzung: <b>KWD</b> + + + + + + Mansardendach +Empfohlene Abkürzung: <b>MD</b> + + + + + + Zeltdach +Empfohlene Abkürzung: <b>ZD</b> + + + + + + Kegeldach +Empfohlene Abkürzung: <b>KeD</b> + + + + + + Kuppeldach +Empfohlene Abkürzung: <b>KuD</b> + + + + + + Sheddach +Empfohlene Abkürzung: <b>ShD</b> + + + + + + Bogendach +Empfohlene Abkürzung: BD + + + + + + Turmdach +Empfohlene Abkürzung: TuD + + + + + + Tonnendach +Empfohlene Abkürzung: ToD + + + + + + Gemischte Dachform +Empfohlene Abkürzung: <b>GDF</b> + + + + + + Sonstige Dachform +Empfohlene Abkürzung: SDF + + + + + + + + + + + Zusammenfassung von Parametern zur Festlegung der zulässigen Dachformen. + + + + + + + + + + + xplan:BP_DetailDachform + + + + + + + + + + + + + + + + + + + Gestaltungs-Festsetzung der Firstrichtung, beruhend auf Landesrecht, gemäß §9 Abs. 4 BauGB. + + + + + + + + + + + + + + + + + + Fläche, auf der ganz oder teilweise nur Wohngebäude, die mit Mitteln der sozialen Wohnraumförderung gefördert werden könnten, errichtet werden dürfen (§9, Abs. 1, Nr. 7 BauGB). + + + + + + + + + + + + + + + + + + Grundrissfläche eines existierenden Gebäudes + + + + + + + + + + + + + + + + + + Fläche für Gemeinschaftsanlagen für bestimmte räumliche Bereiche wie Kinderspielplätze, Freizeiteinrichtungen, Stellplätze und Garagen (§ 9 Abs. 1 Nr. 22 BauGB) + + + + + + + + + xplan:BP_DetailZweckbestGemeinschaftsanlagen + + + + + + + + xplan:BP_BaugebietsTeilFlaeche + + + + + + + + + + + + + + + + Zuordnung von Gemeinschaftsanlagen zu Grundstücken. + + + + + + + + + xplan:BP_GemeinschaftsanlagenFlaeche + + + + + + + + + + + + + + + Aufzählung verschiedener Möglichkeiten, die Bebauung der vorderen, hinteren oder seitlichen Grundstücksgrenzen zu regeln. + + + + + + + Eine Bebauung der Grenze ist verboten. + + + + + + Eine Bebauung der Grenze ist erlaubt. + + + + + + Eine Bebauung der Grenze ist vorgeschrieben. + + + + + + + + + + + Festsetzung einer Fläche für die Einschränkung oder den Ausschluss von Nebenanlagen nach §14 Absatz 1 Satz 3 BauNVO. + + + + + + + + + xplan:BP_TextAbschnitt + + + + + + + + + + + + + + + Aufzählung verschiedener Möglichkeiten, die Errichtung von Nebenanlagen einzuschränken oder auszuschließen. + + + + + + + Die Errichtung bestimmter Nebenanlagen ist eingeschränkt. + + + + + + Die Errichtung bestimmter Nebenanlagen ist ausgeschlossen. + + + + + + + + + + + Fläche für Nebenanlagen, die auf Grund anderer Vorschriften für die Nutzung von Grundstücken erforderlich sind, wie Spiel-, Freizeit- und Erholungsflächen sowie die Fläche für Stellplätze und Garagen mit ihren Einfahrten (§9 Abs. 1 Nr. 4 BauGB) + + + + + + + + + xplan:BP_DetailZweckbestNebenanlagen + + + + + + + + + + + + + + + + + Festlegung der nicht-überbaubaren Grundstücksfläche + + + + + + + + xplan:BP_NutzungNichUueberbaubGrundstFlaeche + + + + + + + + + + + + + + + + Fläche, auf denen ganz oder teilweise nur Wohngebäude errichtet werden dürfen, die für Personengruppen mit besonderem Wohnbedarf bestimmt sind (§9, Abs. 1, Nr. 8 BauGB) + + + + + + + + + + + + + + + + + + Festsetzung nach §9 Abs. 2b BauGB (Zulässigkeit von Vergnügungsstätten). + + + + + + + + + + + + + + + + + + Festsetzung der speziellen Bauweise / baulichen Besonderheit eines Gebäudes oder Bauwerks. + + + + + + + + + xplan:BP_SpezielleBauweiseSonstTypen + + + + + + + + + + + xplan:BP_Wegerecht + + + + + + + + + + + + + + + Aufzählung verschiedener Typen spezieller Bauweisen. + + + + + + Durchfahrt + + + + + + Durchgang + + + + + + Durchfahrt oder Durchgang + + + + + + Auskragung + + + + + + Arkade + + + + + + Luftgeschoss + + + + + + Brücke + + + + + + Tunnel + + + + + + Rampe + + + + + + Sonstige spezielle Bauweise. + + + + + + + + + + + Festsetzung der überbaubaren Grundstücksfläche (§9, Abs. 1, Nr. 2 BauGB). Über die Attribute <i>geschossMin </i>und <i>geschossMax </i>kann die Festsetzung auf einen Bereich von Geschossen beschränkt werden. Wenn eine Einschränkung der Festsetzung durch expliziter Höhenangaben erfolgen soll, ist dazu die Oberklassen-Relation <i>hoehenangabe </i>auf den komplexen Datentyp <i>XP_Hoehenangabe </i>zu verwenden. + +Die gleichzeitige Belegung <b>desselben Attributs </b>in <i>BP_BaugebietsTeilFlaeche </i>und einem überlagernden Objekt <i>BP_UeberbaubareGrunsdstuecksFlaeche </i><b>sollte verzichtet werden</b>. Ab Version 6.0 wird dies evtl. durch eine Konformitätsregel erzwungen. + + + + + + + + + + + + + + + xplan:BP_DetailDachform + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xplan:BP_AbweichendeBauweise + + + + + + + + + + + + + xplan:BP_BauGrenze + + + + + + + xplan:BP_BauLinie + + + + + + + + + + + + + + + + + + + + Generelle Zulässigkeit + + + + + + Generelle Nicht-Zulässigkeit. + + + + + + Ausnahmsweise Zulässigkeit + + + + + + + + + + + + + Gemeinschaftliche Stellplätze + + + + + + Gemeinschaftsgaragen + + + + + + Spielplatz + + + + + + Carport + + + + + + Gemeinschafts-Tiefgarage + + + + + + Nebengebäude + + + + + + Abfall-Sammelanlagen + + + + + + Energie-Verteilungsanlagen + + + + + + Abfall-Wertstoffbehälter + + + + + + Freizeiteinrichtungen + + + + + + Lärmschutz-Anlagen + + + + + + Anlagen für Abwasser oder Regenwasser + + + + + + Fläche für Ausgleichsmaßnahmen + + + + + + Sonstige Zweckbestimmung + + + + + + Fahrrad Stellplätze + + + + + + Gemeinschaftlich genutzter Dachgarten + + + + + + Gemeinschaftlich nutzbare Dachflächen. + + + + + + + + + + + + + Stellplätze + + + + + + Garagen + + + + + + Spielplatz + + + + + + Carport + + + + + + Tiefgarage + + + + + + Nebengebäude + + + + + + Sammelanlagen für Abfall. + + + + + + Energie-Verteilungsanlagen + + + + + + Abfall-Wertstoffbehälter + + + + + + Fahrrad Stellplätze + + + + + + Sonstige Zweckbestimmung + + + + + + + + + + + Fläche, auf denen der Rückbau, die Änderung oder die Nutzungsänderung baulichen Anlagen der Genehmigung durch die Gemeinde bedarf (§172 BauGB) + +Die Klasse wird als <b>veraltet </b>gekennzeichnet und fällt in XPlanGML V. 6.0 weg. Stattdessen sollte die Klasse SO_Gebiet verwendet werden. + + + + + + + + + + + + + + + + + Aufzählung der Gründe für eine Erhaltungssatzung + +Die Enumeration wird als <b>veraltet </b>gekennzeichnet und fällt in XPlanGML V. 6.0 weg. + + + + + + Erhaltung der städtebaulichen Eigenart des Gebiets auf Grund seiner städtebaulichen Gestalt + + + + + + Erhaltung der Zusammensetzung der Wohnbevölkerung + + + + + + Erhaltung bei städtebaulichen Umstrukturierungen + + + + + + + + + + + Einrichtungen und Anlagen zur Versorgung mit Gütern und Dienstleistungen des öffentlichen und privaten Bereichs, hier Flächen für den Gemeindebedarf (§9, Abs. 1, Nr.5 und Abs. 6 BauGB). + + + + + + + + + + + + + + + xplan:BP_DetailDachform + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xplan:BP_DetailZweckbestGemeinbedarf + + + + + + + + xplan:BP_AbweichendeBauweise + + + + + + + + + + + + + + + + + + Einrichtungen und Anlagen zur Versorgung mit Gütern und Dienstleistungen des öffentlichen und privaten Bereichs, hier Flächen für Sport- und Spielanlagen (§9, Abs. 1, Nr. 5 und Abs. 6 BauGB). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xplan:BP_DetailZweckbestSpielSportanlage + + + + + + + + + + + + + + + + + Lärmemissionskontingent eines Teilgebietes nach DIN 45691, Abschnitt 4.6 + + + + + + + + + + + + + + + + + + + + + Lärmemissionskontingent eines Teilgebietes, das einem bestimmten Immissionsgebiet außerhalb des Geltungsbereiches des BPlans zugeordnet ist (Anhang A4 von DIN 45691). + + + + + + + + + + + + + + + + + + + + + + + Spezifikation von Zusatzkontingenten Tag/Nacht der Lärmemission für einen Richtungssektor + + + + + + + + + + + + + + + + + + + + + + Linienhafte Repräsentation einer Richtungssektor-Grenze + + + + + + + + + + + + + + + + + + Parametrische Spezifikation von zusätzlichen Lärmemissionskontingenten für einzelne Richtungssektoren (DIN 45691, Anhang 2). + + + + + + + + + + + + + + + + + + + Flächenhafte Spezifikation von zusätzlichen Lärmemissionskontingenten für einzelne Richtungssektoren (DIN 45691, Anhang 2). + + + + + + + + + + + + + + + + + + + Festsetzungen von öffentlichen und privaten Grünflächen (§ 9, Abs. 1, Nr. 15 BauGB). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xplan:BP_DetailZweckbestGruenFlaeche + + + + + + + + + + + + + + + + + + Fläche für die Errichtung von Anlagen für die Kleintierhaltung wie Ausstellungs- und Zuchtanlagen, Zwinger, Koppeln und dergleichen (§ 9 Abs. 1 Nr. 19 BauGB). + + + + + + + + + + + + + + + + + Festsetzungen für die Landwirtschaft (§ 9, Abs. 1, Nr. 18a BauGB) + +Die Klasse wird als veraltet gekennzeichnet und wird in Version 6.0 wegfallen. Es sollte stattdessen die Klasse BP_LandwirtschaftsFlaeche verwendet werden. + + + + + + + + + xplan:BP_DetailZweckbestLandwirtschaft + + + + + + + + + + + + + + + + Festsetzungen für die Landwirtschaft (§ 9, Abs. 1, Nr. 18a BauGB) + + + + + + + + + xplan:BP_DetailZweckbestLandwirtschaft + + + + + + + + + + + + + + + + Festsetzung von Waldflächen (§ 9, Abs. 1, Nr. 18b BauGB). + + + + + + + + + xplan:BP_DetailZweckbestWaldFlaeche + + + + + + + + + + + + + + + + + + Festsetzung des Anpflanzens von Bäumen, Sträuchern und sonstigen Bepflanzungen; +Festsetzung von Bindungen für Bepflanzungen und für die Erhaltung von Bäumen, Sträuchern und sonstigen Bepflanzungen sowie von Gewässern; (§9 Abs. 1 Nr. 25 und Abs. 4 BauGB) + + + + + + + + + + + + + xplan:BP_VegetationsobjektTypen + + + + + + + + + + + + + + + + + + Festsetzung einer Fläche zum Ausgleich im Sinne des § 1a Abs.3 und §9 Abs. 1a BauGB. + + + + + + + + + + + + + + + + + + + + + + Festsetzung einer Einzelmaßnahme zum Ausgleich im Sinne des § 1a Abs.3 und §9 Abs. 1a BauGB. + + + + + + + + + + + + + + + + + + + + + + Bestimmt einen Bereich, in dem ein Eingriff nach dem Naturschutzrecht zugelassen wird, der durch geeignete Flächen oder Maßnahmen ausgeglichen werden muss. + + + + + + + + + + + + + + + + + + Umgrenzung von Flächen für Maßnahmen zum Schutz, zur Pflege und zur Entwicklung von Natur und Landschaft (§9 Abs. 1 Nr. 20 und Abs. 4 BauGB) + + + + + + + + + + + + + + + + + + + + + + + Maßnahmen zum Schutz, zur Pflege und zur Entwicklung von Natur und Landschaft (§9 Abs. 1 Nr. 20 und Abs. 4 BauGB). + + + + + + + + + + + + + + + + + + + + + + + + + Nutzungsarten-Grenze zur Abgrenzung von Baugebieten mit unterschiedlicher Art oder unterschiedlichem Maß der baulichen Nutzung. + + + + + + Abgrenzung von Bereichen mit unterschiedlichen Festsetzungen zur Gebäudehöhe und/oder Zahl der Vollgeschosse. + + + + + + Sonstige Abgrenzung + + + + + + + + + + + Darstellung von Maßpfeilen oder Maßkreisen in BPlänen, um eine eindeutige Vermassung einzelner Festsetzungen zu erreichen. +Bei Masspfeilen (typ == 1000) sollte das Geometrie-Attribut position nur eine einfache Linien (gml:LineString mit 2 Punkten) enthalten +Bei Maßkreisen (typ == 2000) sollte position nur einen einfachen Kreisbogen (gml:Curve mit genau einem gml:Arc enthalten. +In der nächsten Hauptversion von XPlanGML werden diese Empfehlungen zu verpflichtenden Konformitätsbedingungen. + + + + + + + + + + + + + + + + + + + + + Festsetzung nach § 9 Nr. (4) BauGB. + + + + + + + + + + + + + + + + + + Fläche, für die keine geplante Nutzung angegeben werden kann + + + + + + + + + + + + + + + + + Umgrenzung der Flächen, die von der Bebauung freizuhalten sind, und ihre Nutzung (§ 9 Abs. 1 Nr. 10 BauGB). + + + + + + + + + + + + + + + + + + + Klasse zur Modellierung aller Inhalte des Bebauungsplans,die durch keine andere spezifische XPlanung Klasse repräsentiert werden können. + + + + + + + + xplan:BP_ZweckbestimmungGenerischeObjekte + + + + + + + + + + + + + + + + Festsetzungen nach §9 Abs. 1 Nr. 1 BauGB für übereinanderliegende Geschosse und Ebenen und sonstige Teile baulicher Anlagen (§9 Abs.3 BauGB), sowie Hinweise auf Geländehöhen. Die Höhenwerte werden über das Attribut <i>hoehenangabe </i>der Basisklasse <i>XP_Objekt </i>spezifiziert. + + + + + + + + + + + + + + + + + Flächen für Kennzeichnungen gemäß §9 Abs. 5 BauGB. + + + + + + + + + + + + + + + + + + + + Abgrenzung unterschiedlicher Nutzung, z.B. von Baugebieten wenn diese nach PlanzVO in der gleichen Farbe dargestellt werden, oder Abgrenzung unterschiedlicher Nutzungsmaße innerhalb eines Baugebiets ("Knödellinie", § 1 Abs. 4, § 16 Abs. 5 BauNVO). + + + + + + + + + xplan:BP_DetailAbgrenzungenTypen + + + + + + + + + + + + + + + + Flächenhafte Festlegung einer Sichtfläche bzw. eines Sichtdreiecks + +In Version 6.0 wird diese Klasse evtl. in der Modellbereich "Sonstige Planwerke" transferiert. + + + + + + + + + + + + + + + + + + + + Aufzählung der Typen von Sichtflächen + + + + + + Haltesichtweite + + + + + + Anfahrsichtfeld + + + + + + Annäherungssichtfeld + + + + + + Sichtfeld an Überquerungsstellen + + + + + + Sonstige Sichtfläche + + + + + + + + + + Aufzählung von Kombinationen sich treffender Straßentypen + + + + + + Knotenpunkt Anliegerstraße - Anliegerweg + + + + + + Knotenpunkt Anliegerstraße - Anliegerstraße + + + + + + Knotenpunkt Sammelstraße - Anliegerstraße + + + + + + Knotenpunkt mit einer Haupt-Sammelstraße + + + + + + Knotenpunkt mit einer angebaute Hauptverkehrsstraße (Bebauung parallel zur Straße ist vorhanden) + + + + + + Knotenpunkt mit einer nicht angebaute Hauptverkehrsstraße (Keine Bebauung parallel zur Straße ) + + + + + + Sonstiger Knotenpunkt + + + + + + + + + + + Bereich, in dem bestimmte Textliche Festsetzungen gültig sind, die über die Relation "<i>refTextInhalt</i>" (Basisklasse <i>BP_Objekt</i>) spezifiziert werden. + + + + + + + + + + + + + + + + + Unverbindliche Vormerkung späterer Planungsabsichten. + + + + + + + + + + + + + + + + + + + Ausweisung einer Veränderungssperre, die nicht den gesamten Geltungsbereich des Plans umfasst. Bei Verwendung dieser Klasse muss das Attribut "<i>veraenderungssperre</i>" des zugehörigen Plans (Klasse <i>BP_Plan</i>) auf "<i>false</i>" gesetzt werden. + + + + + + + + + + + + + + + + + + + + + + Festsetzung von Flächen, die mit Geh-, Fahr-, und Leitungsrechten zugunsten der Allgemeinheit, eines Erschließungsträgers, oder eines beschränkten Personenkreises belastet sind (§ 9 Abs. 1 Nr. 21 und Abs. 6 BauGB). + + + + + + + + + + + + + + + + + + + + + + + + Gehrecht + + + + + + Fahrrecht + + + + + + Radfahrrecht + + + + + + Geh- und Fahrrecht. + +Dieser Enumerationswert ist veraltet und wird in in Version 6.0 wegfallen. Stattdessen sollte das Attribut typ zweimal mit den Codes 1000 und 2000 belegt werden. + + + + + + Leitungsrecht + + + + + + Geh- und Leitungsrecht + +Dieser Enumerationswert ist veraltet und wird in in Version 6.0 wegfallen. Stattdessen sollte das Attribut typ zweimal mit den Codes 1000 und 4000 belegt werden. + + + + + + Fahr- und Leitungsrecht + +Dieser Enumerationswert ist veraltet und wird in in Version 6.0 wegfallen. Stattdessen sollte das Attribut typ zweimal mit den Codes 2000 und 4000 belegt werden. + + + + + + Geh-, Fahr- und Leitungsrecht + +Dieser Enumerationswert ist veraltet und wird in in Version 6.0 wegfallen. Stattdessen sollte das Attribut typ dreimal mit den Codes 1000, 2000 und 4000 belegt werden. + + + + + + Sonstiges Nutzungsrecht + + + + + + + + + + + + + Das Objekt definiert einen Maßpfeil + + + + + + Das Objekt definiert einen Maßkreis + + + + + + + + + + + Festsetzung einer von der Bebauung freizuhaltenden Schutzfläche und ihre Nutzung, sowie einer Fläche für besondere Anlagen und Vorkehrungen zum Schutz vor schädlichen Umwelteinwirkungen und sonstigen Gefahren im Sinne des Bundes-Immissionsschutzgesetzes sowie die zum Schutz vor solchen Einwirkungen oder zur Vermeidung oder Minderung solcher Einwirkungen zu treffenden baulichen und sonstigen technischen Vorkehrungen (§9, Abs. 1, Nr. 24 BauGB). + + + + + + + + + + + + xplan:BP_DetailTechnVorkehrungImmissionsschutz + + + + + + + + + + + + + + + + + + Lärmpegelbereich I nach DIN 4109. + + + + + + Lärmpegelbereich II nach DIN 4109. + + + + + + Lärmpegelbereich III nach DIN 4109. + + + + + + Lärmpegelbereich IV nach DIN 4109. + + + + + + Lärmpegelbereich V nach DIN 4109. + + + + + + Lärmpegelbereich VI nach DIN 4109. + + + + + + Lärmpegelbereich VII nach DIN 4109. + + + + + + + + + + + Fläche für technische oder bauliche Maßnahmen nach § 9, Abs. 1, Nr. 23 BauGB. + + + + + + + + + + + + + + + + + + + + + + + + + + + "Von der Bebauung freizuhaltende Schutzfläche" nach §9 Abs. 1 Nr. 24 BauGB + + + + + + "Fläche für besondere Anlagen und Vorkehrungen zum Schutz vor schädlichen Umwelteinwirkungen" nach §9 Abs. 1 Nr. 24 BauGB + + + + + + + + + + + + + Allgemeine Lärmschutzvorkehrung + + + + + + Fassaden mit Schallschutzmaßnahmen + + + + + + Lärmschutzwand + + + + + + Lärmschutzwall + + + + + + Sonstige Vorkehrung zum Immissionsschutz + + + + + + + + + + + + + Gebiete, in denen zum Schutz vor schädlichen Umwelteinwirkungen im Sinne des Bundes-Immissionsschutzgesetzes bestimmte Luft-verunreinigende Stoffe nicht oder nur beschränkt verwendet werden dürfen (§9, Abs. 1, Nr. 23a BauGB). + + + + + + Gebiete in denen bei der Errichtung von Gebäuden bestimmte bauliche Maßnahmen für den Einsatz erneuerbarer Energien wie insbesondere Solarenergie getroffen werden müssen (§9, Abs. 1, Nr. 23b BauGB). + + + + + + Gebiete, in denen bei der Errichtung von nach Art, Maß oder Nutzungsintensität zu bestimmenden Gebäuden oder sonstigen baulichen Anlagen in der Nachbarschaft von Betriebsbereichen nach § 3 Absatz 5a des Bundes-Immissionsschutzgesetzes bestimmte bauliche und sonstige technische Maßnahmen, die der Vermeidung oder Minderung der Folgen von Störfällen dienen, getroffen werden müssen (§9, Abs. 1, Nr. 23c BauGB). + + + + + + + + + + + Flächen und Leitungen für Versorgungsanlagen, für die Abfallentsorgung und Abwasserbeseitigung sowie für Ablagerungen (§9 Abs. 1, Nr. 12, 14 und Abs. 6 BauGB) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xplan:BP_DetailZweckbestVerEntsorgung + + + + + + + + + + + + + + + + + + Zentraler Versorgungsbereich gem. § 9 Abs. 2a BauGB + + + + + + + + + + + + + + + + + Bereich ohne Ein- und Ausfahrt (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB). +Durch die Digitalisierungsreihenfolge der Linienstützpunkte muss sichergestellt sein, dass der angrenzende Bereich ohne Ein- und Ausfahrt relativ zur Laufrichtung auf der linken Seite liegt. + + + + + + + + + + + + + + + + + Aufzählung verschiedener Typen von Bereichen ohne Ein- und Ausfahrt. + + + + + + + Bereich ohne Einfahrt + + + + + + Bereich ohne Ausfahrt + + + + + + Bereich ohne Ein- und Ausfahrt. + + + + + + + + + + + Punktförmig abgebildete Einfahrt (§9 Abs. 1 Nr. 11 und Abs. 6 BauGB). + + + + + + + + + + + + + + + + + + Linienhaft modellierter Einfahrtsbereich (§9 Abs. 1 Nr. 11 und Abs. 6 BauGB). +Durch die Digitalisierungsreihenfolge der Linienstützpunkte muss sichergestellt sein, dass die angrenzende Einfahrt relativ zur Laufrichtung auf der linken Seite liegt. + + + + + + + + + + + + + + + + + + Straßenbegrenzungslinie (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB) . +Durch die Digitalisierungsreihenfolge der Linienstützpunkte muss sichergestellt sein, dass die abzugrenzende Straßenfläche relativ zur Laufrichtung auf der linken Seite liegt. + + + + + + + + + + + + + + + + + + Flächen für Aufschüttungen, Abgrabungen und Stützmauern, soweit sie zur Herstellung des Straßenkörpers erforderlich sind (§9, Abs. 1, Nr. 26 BauGB). + + + + + + + + + + + + + + + + + + Aufzählung der möglichen Maßnahmen zur Herstellung des Straßenkörpers + + + + + + Aufschüttung + + + + + + Abgrabung + + + + + + Stützmauer + + + + + + + + + + + Strassenverkehrsfläche (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB) . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xplan:BP_StrassenbegrenzungsLinie + + + + + + + + + + + + + + + + Verkehrsfläche besonderer Zweckbestimmung (§ 9 Abs. 1 Nr. 11 und Abs. 6 BauGB). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xplan:BP_DetailZweckbestStrassenverkehr + + + + + + + + xplan:BP_StrassenbegrenzungsLinie + + + + + + + + + + + + + + + + Aufzählung der möglichen besonderen Zweckbestimmungen einer Strassen-Verkehrsfläche. + + + + + + Fläche für das Parken von Fahrzeugen + + + + + + Fußgängerbereich + + + + + + Verkehrsberuhigte Zone + + + + + + Rad- und Fußweg + + + + + + Reiner Radweg + + + + + + Reiner Fußweg + + + + + + Wanderweg + + + + + + Reit- oder Kutschweg + + + + + + Wirtschaftsweg + + + + + + Abstellplatz für Fahrräder + + + + + + Brückenbereich, hier der überführende Verkehrsweg. + + + + + + Brückenbereich, hier der unterführende Verkehrsweg. + + + + + + Park-and-Ride Anlage + + + + + + Platz + + + + + + Anschlussfläche + + + + + + Landwirtschaftlicher Verkehr + + + + + + Verkehrsgrün + + + + + + Rastanlage + + + + + + Busbahnhof + + + + + + Fläche zum Car-Sharing + + + + + + Fläche zum Abstellen gemeinschaftlich genutzter Fahrräder + + + + + + Bike and Ride Anlage + + + + + + Parkhaus + + + + + + Mischverkehrsfläche + + + + + + Ladestation für Elektrofahrzeuge + + + + + + Sonstige Zweckbestimmung Straßenverkehr. + + + + + + + + + + + + + Nur Einfahrt möglich + + + + + + Nur Ausfahrt möglich + + + + + + Ein- und Ausfahrt möglich + + + + + + + + + + + Festsetzung neuer Wasserflächen nach §9 Abs. 1 Nr. 16a BauGB. +Diese Klasse wird in der nächsten Hauptversion des Standards eventuell wegfallen und durch SO_Gewaesser ersetzt werden. + + + + + + + + + xplan:BP_DetailZweckbestGewaesser + + + + + + + + + + + + + + + + Flächen für die Wasserwirtschaft (§9 Abs. 1 Nr. 16a BauGB), sowie Flächen für Hochwasserschutz-anlagen und für die Regelung des Wasserabflusses (§9 Abs. 1 Nr. 16b BauGB). + + + + + + + + + xplan:BP_DetailZweckbestWasserwirtschaft + + + + + + + + + + \ No newline at end of file diff --git a/tests/xsd/XPlanGML_Basisschema.xsd b/tests/xsd/XPlanGML_Basisschema.xsd new file mode 100644 index 0000000..b827c03 --- /dev/null +++ b/tests/xsd/XPlanGML_Basisschema.xsd @@ -0,0 +1,3565 @@ + + + + + + + + + + + + + Absolute Höhenangabe im Bezugssystem NHN + + + + + + Absolute Höhenangabe im Bezugssystem NN + + + + + + Absolute Höhenangabe im Bezugssystem DHHN + + + + + + Höhenangabe relativ zur Geländeoberkante an der Position des Planinhalts. + + + + + + Höhenangabe relativ zur Gehweg-Oberkante an der Position des Planinhalts. + + + + + + Höhenangabe relativ zu der auf Planebene festgelegten absoluten Bezugshöhe (Attribut <i>bezugshoehe </i>von <i>XP_Plan</i>). + + + + + + + Höhenangabe relativ zur Strassenoberkante an der Position des Planinhalts + + + + + + + + + + + + + Traufhöhe als Höhenbezugspunkt + + + + + + Firsthöhe als Höhenbezugspunkt. + + + + + + Oberkante als Höhenbezugspunkt. + + + + + + Lichte Höhe + + + + + + Sockelhöhe + + + + + + Erdgeschoss Fußbodenhöhe + + + + + + Höhe Baulicher Anlagen + + + + + + Unterkante + + + + + + Gebäudehöhe + + + + + + Wandhöhe + + + + + + Geländeoberkante + + + + + + + + + + Aufzählung möglicher Bedeutungen eines Planbereichs. + + + + + + Räumliche oder sachliche Aufteilung der Planinhalte. + + + + + + Aggregation von Objekten außerhalb des Geltungsbereiches gemäß Eingriffsregelung. + + + + + + Bereich, für den keine der aufgeführten Bedeutungen zutreffend ist. In dem Fall kann die Bedeutung über das Textattribut "<i>detaillierteBedeutung"</i> angegeben werden. + + + + + + + + + + + Ein Abschnitt der Begründung des Plans. + + + + + + + + + + + + + + + + + + + + Abstrakte Oberklasse für die Modellierung von Bereichen. Ein Bereich fasst die Inhalte eines Plans nach bestimmten Kriterien zusammen. + + + + + + + + + + + + + + + xplan:XP_Rasterdarstellung + + + + + + + xplan:XP_Objekt + xplan:gehoertZuBereich + + + + + + + xplan:XP_AbstraktesPraesentationsobjekt + xplan:gehoertZuBereich + + + + + + + + + + + + + + + + Generische Attribute vom Datentyp "Datum" + + + + + + + + + + + + + + + + + + + + + + Generisches Attribut vom Datentyp "Double". + + + + + + + + + + + + + + + + + + + + + + Verweis auf ein extern gespeichertes Dokument oder einen extern gespeicherten, georeferenzierten Plan. Einer der beiden Attribute "<i>referenzName</i>" bzw. "<i>referenzURL</i>" muss belegt sein. + + + + + + + xplan:XP_MimeTypes + + + + + + + + + + + xplan:XP_MimeTypes + + + + + + + + + + + + + + + + + + + + Typisierung von referierten Dokumenten. + + + + + Referenz auf ein Dokument. + + + + + Referenz auf einen georeferenzierten Plan. + + + + + + + + + + + Spezifikation einer für die Aufstellung des Plans zuständigen Gemeinde. + + + + + + + + + + + + + + + + + + + + + + Abstrakte Basisklasse für Generische Attribute. + + + + + + + + + + + + + + + + + + Spezifikation einer Angabe zur vertikalen Höhe oder zu einem Bereich vertikaler Höhen. Es ist möglich, spezifische Höhenangaben (z.B. die First- oder Traufhöhe eines Gebäudes) vorzugeben oder einzuschränken, oder den Gültigkeitsbereich eines Planinhalts auf eine bestimmte Höhe (<i>hZwingend</i>) bzw. einen Höhenbereich (<i>hMin </i>- <i>hMax</i>) zu beschränken, was vor allem bei der höhenabhängigen Festsetzung einer überbaubaren Grundstücksfläche (<i>BP_UeberbaubareGrundstuecksflaeche</i>), einer Baulinie (<i>BP_Baulinie</i>) oder einer Baugrenze (<i>BP_Baugrenze</i>) relevant ist. In diesem Fall bleiben die Attribute <i>bezugspunkt und abweichenderBezugspunkt </i>unbelegt. + + + + + + + + + + + + + + + + + + + + + + + + + + Generische Attribute vom Datentyp "Integer". + + + + + + + + + + + + + + + + + + + + + + Abstrakte Oberklasse für alle XPlanung-Fachobjekte. Die Attribute dieser Klasse werden über den Vererbungs-Mechanismus an alle Fachobjekte weitergegeben. + + + + + + + + + + + xplan:XP_GesetzlicheGrundlage + + + + + + + + + + + + + xplan:XP_Bereich + xplan:planinhalt + + + + + + + xplan:XP_AbstraktesPraesentationsobjekt + xplan:dientZurDarstellungVon + + + + + + + xplan:XP_BegruendungAbschnitt + + + + + + + xplan:XP_WirksamkeitBedingung + + + + + + + xplan:XP_WirksamkeitBedingung + + + + + + + + + + + + + + + + + Abstrakte Oberklasse für alle Klassen raumbezogener Pläne. + + + + + + + + + + + + + + + + + + + + + + + + + xplan:XP_TextAbschnitt + + + + + + + xplan:XP_BegruendungAbschnitt + + + + + + + + + + + + + + + + Spezifikation der Institution, die für den Plan verantwortlich ist. + + + + + + + + + + + + + + + + + + + Rechtscharakter einer Planänderung + + + + + + <b>Änderung </b>eines Planes: Der Geltungsbereich des neueren Plans überdeckt nicht den gesamten Geltungsbereich des Ausgangsplans. Im Überlappungsbereich gilt das neuere Planrecht. + + + + + + <b>Ergänzung </b>eines Plans: Die Inhalte des neuen Plans ergänzen die alten Inhalte, z.B. durch zusätzliche textliche Planinhalte oder Überlagerungsobjekte. Die Inhalte des älteren Plans bleiben aber gültig. + + + + + + <b>Aufhebung </b>des Plans: Der Geltungsbereich des neuen Plans überdeckt den alten Plan, und die Inhalte des neuen Plans ersetzen die alten Inhalte vollständig. + + + + + + Das altes Planrecht wurde durch ein förmliches Verfahren aufgehoben + + + + + + Der alte Plan tritt ohne förmliches Verfahren außer Kraft + + + + + + + + + + Aufzählung der Planungszustände eines Planinhalts. + + + + + + + Der Planinhalt bezieht sich auf eine Planung + + + + + + Der Planinhalt stellt den aktuellen Zustand dar. + + + + + + Der Planinhalt beschreibt einen zukünftig fortfallenden Zustand. + + + + + + + + + + + Spezifikation der Attribute für einer Schutz-, Pflege- oder Entwicklungsmaßnahme. + + + + + + + + + + + + + + + + + + + + Aufzählung der Typen von Ausgleichs- und Ersatzmaßnahmen. + + + + + + + Artenreicher Gehölzbestand ist aus unterschiedlichen, standortgerechten Gehölzarten aufgebaut und weist einen Strauchanteil auf. + + + + + + Naturnahe Wälder zeichnen sich durch eine standortgemäße Gehölzzusammensetzung unterschiedlicher Altersstufen, durch eine Schichtung der Gehölze (z.B. Strauchschicht, sich überlagernder erster Baumschicht in 10-15 m Höhe und zweiter Baumschicht in 20-25 m Höhe) sowie durch eine in der Regeln artenreiche Krautschicht aus. Kennzeichnend sind zudem das gleichzeitige Nebeneinander von aufwachsenden Gehölzen, Altbäumen und Lichtungen in kleinräumigen Wechsel sowie ein gewisser Totholzanteil. + + + + + + Gegenüber einer intensiven Nutzung sind bei extensiver Grünlandnutzung sowohl Beweidungsintensitäten als auch der Düngereinsatz deutlich geringer. Als Folge finden eine Reihe von eher konkurrenzschwachen, oft auch trittempflindlichen Pflanzenarten Möglichkeiten, sich neben den in der Regel sehr robusten, wuchskräftigen, jedoch sehr nährstoffbedürftigen Pflanzen intensiver Wirtschaftsflächen zu behaupten. Dadurch kommt es zur Ausprägung von standortbedingt unterschiedlichen Grünlandgesellschaften mit deutlichen höheren Artenzahlen (größere Vielfalt). + + + + + + Artenreiches Feuchtgrünland entwickelt sich bei extensiver Bewirtschaftung auf feuchten bis wechselnassen Standorten. Die geringe Tragfähigkeit des vielfach anstehenden Niedermoorbodens erschwert den Einsatz von Maschinen, so dass die Flächen vorwiegend beweidet bzw. erst spät im Jahr gemäht werden. + + + + + + Obstwiesen umfassen mittel- oder hochstämmige, großkronige Obstbäume auf beweidetem (Obstweide) oder gemähtem (obstwiese) Grünland. Im Optimalfall setzt sich der aufgelockerte Baumbestand aus verschiedenen, möglichst alten, regional-typischen Kultursorten zusammen. + + + + + + Naturahne Uferbereiche umfassen unterschiedlich zusammengesetzte Röhrichte und Hochstaudenrieder oder Seggen-Gesellschaften sowie Ufergehölze, die sich vorwiegend aus strauch- oder baumförmigen Weiden, Erlen oder Eschen zusammensetzen. + + + + + + Im flachen Wasser oder auf nassen Böden bilden sich hochwüchsige, oft artenarme Bestände aus überwiegend windblütigen Röhrichtarten aus. Naturliche Bestände finden sich im Uferbereich von Still- und Fließgewässern. + + + + + + Ackerrandstreifen sind breite Streifen im Randbereich eines konventionell oder ökologisch genutzten Ackerschlages. + + + + + + Als Ackerbrachflächen werden solche Biotope angesprochen, die seit kurzer Zeit aus der Nutzung herausgenommen worden sind. Sie entstehen, indem Ackerflächen mindestens eine Vegetationsperiode nicht mehr bewirtschaftet werden. + + + + + + Als Grünlandbrachen werden solche Biotope angesprochen, die seit kurzer Zeit aus der Nutzung herausgenommen worden sind. Sie entstehen, indem Grünland mindestens eine Vegetationsperiode nicht mehr bewirtschaftet wird. + + + + + + Sukzessionsflächen umfassen dauerhaft ungenutzte, der natürlichen Entwicklung überlassene Vegetationsbestände auf trockenen bis feuchten Standorten. + + + + + + Hochwüchsige, zumeist artenreiche Staudenfluren feuchter bis nasser Standorte entwickeln sich in der Regel auf Feuchtgrünland-Brachen, an gehölzfreien Uferstreifen oder an anderen zeitweilig gestörten Standorten mit hohen Grundwasserständen. + + + + + + Trockenrasen sind durch zumindest zeitweilige extreme Trockenheit (Regelwasser versickert rasch) sowie durch Nährstoffarmut charakterisiert, die nur Arten mit speziell angepassten Lebensstrategien Entwicklungsmöglichkeiten bieten. + + + + + + Heiden sind Zwergstrauchgesellschaften auf nährstoffarmen, sauren, trockenen (Calluna-Heide) oder feuchten (Erica-Heide) Standorten. Im Binnenland haben sie in der Regel nach Entwaldung (Abholzung) und langer Übernutzung (Beweidung) primär nährstoffarmer Standorte entwickelt. + + + + + + Sonstiges + + + + + + + + + + + Ergänzung des Datentyps XP_ExterneReferenz um ein Attribut zur semantischen Beschreibung des referierten Dokuments. + + + + + + + + + + + + + + + + + + + + + + + Generisches Attribut vom Datentyp "CharacterString" + + + + + + + + + + + + + + + + + + + + + + Ein Abschnitt der textlich formulierten Inhalte des Plans. + + + + + + + + + + + + + + + + + + + + + Generische Attribute vom Datentyp "URL" + + + + + + + + + + + + + + + + + + + + + + Spezifikation eines anderen Plans, der mit dem Ausgangsplan verbunden ist und diesen ändert bzw. von ihm geändert wird. + + + + + + + + + + xplan:XP_Plan + + + + + + + + + + + + + + + + + + + Vermerk eines am Planungsverfahrens beteiligten Akteurs. + + + + + + + + + + + + + + + + + + + + + + + Spezifikation von Bedingungen für die Wirksamkeit oder Unwirksamkeit einer Festsetzung. + + + + + + + + + + + + + + + + + + + + + + + Beschreibung eines Plans + + + + + + Begründung eines Plans + + + + + + Plan-Legende + + + + + + Elektronische Version des rechtsverbindlichen Plans + + + + + + Elektronische Version der Plangrundlage, z.B. ein katasterplan + + + + + + Umweltbericht - Ergebnis der Umweltprügung bzgl. der Umweltbelange + + + + + + Satzung + + + + + + Elektronische Version des Verordnungstextes + + + + + + Referenz auf eine Karte, die in Bezug zum Plan steht + + + + + + Erläuterungsbericht + + + + + + Zusammenfassende Erklärung der in dem Verfahren berücksichtigten Umweltbelange gemäß §10 Absatz 4 BauGB. + + + + + + Koordinaten-Liste + + + + + + Grundstücksverzeichnis + + + + + + Pflanzliste + + + + + + Grünordnungsplan + + + + + + Erschließungsvertrag + + + + + + Durchführungsvertrag + + + + + + Elektronische Version eines städtebaulichen Vertrages + + + + + + Elentronisches Dokument mit umweltbezogenen Stellungnahmen. + + + + + + Dokument mit den Beschluss des Gemeinderats zur öffentlichen Auslegung. + + + + + + Referenz auf einen Vorhaben- und Erschließungsplan nach §7 BauBG-MaßnahmenG von 1993 + + + + + + Referenz auf den Metadatensatz des Plans + + + + + + Referenz auf ein Dokument mit dem Text der Genehmigung + + + + + + Referenz auf den Bekanntmachungs-Text + + + + + + Sonstiges rechtsverbindliches Dokument + + + + + + Sonstiges nicht-rechtsverbindliches Dokument + + + + + + + + + + Liste der nach §9, Abs.1, Nr. 25 möglichen Maßnahmen + + + + + + + Bindungen für Bepflanzungen und für die Erhaltung von Bäumen, Sträuchern und sonstigen Bepflanzungen sowie von Gewässern. Dies entspricht dem Planzeichen 13.2.2 der PlanzV 1990. + + + + + + Anpflanzung von Bäumen, Sträuchern oder sonstigen Bepflanzungen. Dies entspricht dem Planzeichen 13.2.1 der PlanzV 1990. + + + + + + Anpflanzen von Bäumen, Sträuchern und sonstigen Bepflanzungen, sowie Bindungen für Bepflanzungen und für die Erhaltung von Bäumen, Sträuchern und sonstigen Bepflanzungen sowie von Gewässern + + + + + + + + + + Aufzählung verschiedener Möglichkeiten, von den nach BauNVO standardmäßig festgelegten baulichen Nutzungen abzuweichen. + + + + + + + Einschränkung einer generell erlaubten Nutzung. + + + + + + Ausschluss einer generell erlaubten Nutzung. + + + + + + Eine nur ausnahmsweise zulässige Nutzung wird generell zulässig. + + + + + + Sonstige Abweichung. + + + + + + + + + + Aufzählung der möglichen" Allgemeinen Arten der baulichen Nutztung". + + + + + + + Wohnbaufläche nach §1 Abs. (1) BauNVO + + + + + + Gemischte Baufläche nach §1 Abs. (1) BauNVO. + + + + + + Gewerbliche Baufläche nach §1 Abs. (1) BauNVO. + + + + + + Sonderbaufläche nach §1 Abs. (1) BauNVO. + + + + + + Sonstige Baufläche + + + + + + + + + + Gegenstand der nach §9, Abs. 1, Nr. 25 festgesetzten Maßnahme + + + + + + Bäume + + + + + + Kopfbäume + + + + + + Baumreihe + + + + + + Sträucher + + + + + + Bäume und Sträucher + + + + + + Hecke + + + + + + Knick + + + + + + Sonstige Bepflanzung + + + + + + Gewässer (nur Erhaltung) + + + + + + Fassadenbegrünung + + + + + + Dachbegrünung + + + + + + + + + + Aufzählung der verschiedenen Typen der "Art der Baulichen Nutzung" laut BauGB. + + + + + + Kleinsiedlungsgebiet nach § 2 BauNVO. + + + + + + Reines Wohngebiet nach § 3 BauNVO. + + + + + + Allgemeines Wohngebiet nach § 4 BauNVO. + + + + + + Gebiet zur Erhaltung und Entwicklung der Wohnnutzung (Besonderes Wohngebiet) nach § 4a BauNVO. + + + + + + Dorfgebiet nach $ 5 BauNVO. + + + + + + Mischgebiet nach $ 6 BauNVO. + + + + + + Urbanes Gebiet nach § 6a BauNVO + + + + + + Kerngebiet nach § 7 BauNVO. + + + + + + Gewerbegebiet nach § 8 BauNVO. + + + + + + Industriegebiet nach § 9 BauNVO. + + + + + + Sondergebiet, das der Erholung dient nach § 10 BauNVO von 1977 und 1990. + + + + + + Sonstiges Sondergebiet nach§ 11 BauNVO 1977 und 1990; z.B. Klinikgebiet + + + + + + Wochenendhausgebiet nach §10 der BauNVO von 1962 und 1968 + + + + + + Sondergebiet nach §11der BauNVO von 1962 und 1968 + + + + + + Sonstiges Gebiet + + + + + + + + + + + + + Brandenburg + + + + + + Berlin + + + + + + Baden-Württemberg + + + + + + Bayern + + + + + + Bremen + + + + + + Hessen + + + + + + Hamburg + + + + + + Mecklenburg-Vorpommern + + + + + + Niedersachsen + + + + + + Nordrhein-Westfalen + + + + + + Rheinland-Pfalz + + + + + + Schleswig-Holstein + + + + + + Saarland + + + + + + Sachsen + + + + + + Sachsen-Anhalt + + + + + + Thüringen + + + + + + Der Bund. + + + + + + + + + + + + + Öffentlicher Wald allgemein + + + + + + Staatswald + + + + + + Körperschaftswald + + + + + + Kommunalwald + + + + + + Stiftungswald + + + + + + Privatwald allgemein + + + + + + Gemeinschaftswald + + + + + + Genossenschaftswald + + + + + + Kirchenwald + + + + + + Sonstiger Wald + + + + + + + + + + Aufzählung der verschiedenen Typen von Grenzen. + + + + + + Bundesgrenze + + + + + + Grenze eines Bundeslandes + + + + + + Grenze eines Regierungsbezirks + + + + + + Grenze eines Bezirks. + + + + + + Grenze eines Kreises. + + + + + + Grenze einer Gemeinde. + + + + + + Grenze einer Verbandsgemeinde + + + + + + Grenze einer Samtgemeinde + + + + + + Mitgliedsgemeindegrenze + + + + + + Amtsgrenze + + + + + + Stadtteilgrenze + + + + + + Hinweis auf eine vorgeschlagene Grundstücksgrenze im BPlan. + + + + + + Hinweis auf den Geltungsbereich eines bestehenden BPlan. + + + + + + Sonstige Grenze + + + + + + + + + + Klassifikation von Schutzgebieten nach Naturschutzrecht. + + + + + + Naturschutzgebiet gemäß <font color="#00000a">§23 BNatSchG.</font> + + + + + + Nationalpark <font color="#00000a">gemäß §24 BNatSchG</font> + + + + + + Biosphärenreservat <font color="#00000a">gemäß §25 BNatSchG.</font> + + + + + + Landschaftsschutzgebiet <font color="#00000a">gemäß §65 BNatSchG.</font> + + + + + + Naturpark <font color="#00000a">gemäß §27 BNatSchG.</font> + + + + + + Naturdenkmal <font color="#00000a">gemäß §28 BNatSchG.</font> + + + + + + Geschützter Bestandteil der Landschaft <font color="#00000a">gemäß §29 BNatSchG.</font> + + + + + + Gesetzlich geschützte Biotope <font color="#00000a">gemäß §30 BNatSchG.</font> + + + + + + Schutzgebiet nach Europäischem Recht. Dies umfasst das "Gebiet Gemeinschaftlicher Bedeutung" (FFH-Gebiet) und das "Europäische Vogelschutzgebiet" + + + + + + Gebiete von gemeinschaftlicher Bedeutung + + + + + + Europäische Vogelschutzgebiete + + + + + + Nationales Naturmonument <font color="#00000a">gemäß §24 Abs. (4) BNatSchG.</font> + + + + + + Sonstiges Naturschutzgebiet + + + + + + + + + + Aufzählung möglicher Nutzungsformen einer Fläche + + + + + + + Private Nutzung + + + + + + Öffentliche Nutzung + + + + + + + + + + Aufzählung möglicher Sondernutzungen einer Sonderbaufläche nach §§ 10 und 11 BauNVO. + + + + + + Wochenendhausgebiet + + + + + + Ferienhausgebiet + + + + + + Campingplatzgebiet + + + + + + Kurgebiet + + + + + + Sonstiges Sondergebiet für Erholung + + + + + + Einzelhandelsgebiet + + + + + + Gebiet für großflächigen Einzelhandel + + + + + + Ladengebiet + + + + + + Einkaufszentrum + + + + + + Sonstiges Gebiet für großflächigen Einzelhandel + + + + + + Verkehrsübungsplatz + + + + + + Hafengebiet + + + + + + Sondergebiet für Erneuerbare Energien + + + + + + Militärisches Sondergebiet + + + + + + Sondergebiet Landwirtschaft + + + + + + Sondergebiet Sport + + + + + + Sondergebiet für Gesundheit und Soziales + + + + + + Klinikgebiet + + + + + + Golfplatz + + + + + + Sondergebiet für Kultur + + + + + + Sondergebiet Tourismus + + + + + + Sondergebiet für Büros und Verwaltung + + + + + + Sondergebiet für Einrichtungen der Justiz + + + + + + Sondergebiet Hochschule + + + + + + Sondergebiet für Messe + + + + + + Sonstiges Sondergebiet + + + + + + + + + + Aufzählung der Ziele für Schutz-, Pflege- und Entwicklungsmaßnahmen. + + + + + + + Schutz und Pflege + + + + + + Entwicklung + + + + + + Neu-Anlage + + + + + + Schutz, Pflege und Entwicklung + + + + + + Sonstiges Ziel + + + + + + + + + + + + + Veränderungssperre wurde noch nicht verlängert. + + + + + + Veränderungssperre wurde einmal verlängert. + + + + + + Veränderungssperre wurde zweimal verlängert. + + + + + + + + + + + + + Radfahren + + + + + + Reiten + + + + + + Fahren + + + + + + Hundesport + + + + + + + + + + + + + Einrichtungen und Anlagen für öffentliche Verwaltung + + + + + + Kommunale Einrichtung wie z. B. Rathaus, Gesundheitsamt, Gesundheitsfürsorgestelle, Gartenbauamt, Gartenarbeitsstützpunkt, Fuhrpark. + + + + + + Betrieb mit öffentlicher Zweckbestimmung wie z.B. ein Stadtreinigungsbetrieb, Autobusbetriebshof, Omnibusbahnhof. + + + + + + Eine Anlage des Bundes oder eines Bundeslandes wie z. B. Arbeitsamt, Autobahnmeisterei, Brückenmeisterei, Patentamt, Wasserbauhof, Finanzamt. + + + + + + Sonstige Einrichtung oder Anlage der öffentlichen Verwaltung wie z. B. die Industrie und Handelskammer oder Handwerkskammer. + +Der Eintrag ist <b>veraltet </b>und wird in XPlanGML V. 6.0 entfernt. Es sollte stattdessen der Code 1000 verwendet werden. + + + + + + Einrichtungen und Anlagen für Bildung und Forschung + + + + + + Schulische Einrichtung. Darunter fallen u. a. Allgemeinbildende Schule, Oberstufenzentrum, Sonderschule, Fachschule, Volkshochschule, +Konservatorium. + + + + + + Hochschule, Fachhochschule, Berufsakademie, o. Ä. + + + + + + Berufsbildende Schule + + + + + + Forschungseinrichtung, Forschungsinstitut. + + + + + + Sonstige Anlage oder Einrichtung aus Bildung und Forschung. + + +Der Eintrag ist <b>veraltet </b>und wird in XPlanGML V. 6.0 entfernt. Es sollte stattdessen der Code 1200 verwendet werden. + + + + + + Religiöse Einrichtung + + + + + + Religiösen Zwecken dienendes Gebäude wie z. B. Kirche, + Kapelle, Moschee, Synagoge, Gebetssaal. + + + + + + Religiöses Verwaltungsgebäude, z. B. Pfarramt, Bischöfliches Ordinariat, Konsistorium. + + + + + + Religiöse Gemeinde- oder Versammlungseinrichtung, z. B. Gemeindehaus, Gemeindezentrum. + + + + + + Sonstige religiösen Zwecken dienende Anlage oder Einrichtung. + + +Der Eintrag ist <b>veraltet </b>und wird in XPlanGML V. 6.0 entfernt. Es sollte stattdessen der Code 1400 verwendet werden. + + + + + + Einrichtungen und Anlagen für soziale Zwecke. + + + + + + Soziale Einrichtung für Kinder, wie z. B. Kinderheim, Kindertagesstätte, Kindergarten. + + + + + + Soziale Einrichtung für Jugendliche, wie z. B. Jugendfreizeitheim/-stätte, Jugendgästehaus, Jugendherberge, Jugendheim. + + + + + + Soziale Einrichtung für Familien und Erwachsene, wie z. B. Bildungszentrum, Volkshochschule, Kleinkinderfürsorgestelle, Säuglingsfürsorgestelle, Nachbarschaftsheim. + + + + + + Soziale Einrichtung für Senioren, wie z. B. Alten-/Seniorentagesstätte, Alten-/Seniorenheim, Alten-/Seniorenwohnheim, Altersheim. + + + + + + Sonstige soziale Einrichtung, z. B. Pflegeheim, Schwesternwohnheim, Studentendorf, Studentenwohnheim. Tierheim, Übergangsheim. + + +Der Eintrag ist <b>veraltet </b>und wird in XPlanGML V. 6.0 entfernt. Es sollte stattdessen der Code 1600 verwendet werden. + + + + + + Soziale Einrichtung für Menschen mit Beeinträchtigung, wie z. B. Behindertentagesstätte, Behindertenwohnheim, Behindertenwerkstatt + + + + + + Einrichtungen und Anlagen für gesundheitliche Zwecke. + + + + + + Krankenhaus oder vergleichbare Einrichtung (z. B. Klinik, Hospital, Krankenheim, Heil- und Pflegeanstalt), + + + + + + Sonstige Gesundheits-Einrichtung, z. B. Sanatorium, Kurklinik, Desinfektionsanstalt. + + +Der Eintrag ist <b>veraltet </b>und wird in XPlanGML V. 6.0 entfernt. Es sollte stattdessen der Code 1800 verwendet werden. + + + + + + Einrichtungen und Anlagen für kulturelle Zwecke. + + + + + + Kulturelle Einrichtung aus dem Bereich Musik oder Theater (z. B. Theater, Konzerthaus, Musikhalle, Oper). + + + + + + Kulturelle Einrichtung mit Bildungsfunktion ( z. B. Museum, Bibliothek, Bücherei, Stadtbücherei, Volksbücherei). + + + + + + Sonstige kulturelle Einrichtung, wie z. B. Archiv, Landesbildstelle, Rundfunk und Fernsehen, Kongress- und Veranstaltungshalle, Mehrzweckhalle.. + +Der Eintrag ist <b>veraltet </b>und wird in XPlanGML V. 6.0 entfernt. Es sollte stattdessen der Code 2000 verwendet werden. + + + + + + Einrichtungen und Anlagen für sportliche Zwecke. + + + + + + Schwimmbad, Freibad, Hallenbad, Schwimmhalle o. Ä.. + + + + + + Sportplatz, Sporthalle, Tennishalle o. Ä. + + + + + + Sonstige Sporteinrichtung. + +Der Eintrag ist <b>veraltet </b>und wird in XPlanGML V. 6.0 entfernt. Es sollte stattdessen der Code 2200 verwendet werden. + + + + + + Einrichtungen und Anlagen für Sicherheit und Ordnung. + + + + + + Einrichtung oder Anlage der Feuerwehr. + + + + + + Schutzbauwerk + + + + + + Einrichtung der Justiz, wie z. B. Justizvollzug, Gericht, Haftanstalt. + + + + + + Sonstige Anlage oder Einrichtung für Sicherheit und Ordnung, z. B. Polizei, Zoll, Feuerwehr, Zivilschutz, Bundeswehr, Landesverteidigung. + +Der Eintrag ist <b>veraltet </b>und wird in XPlanGML V. 6.0 entfernt. Es sollte stattdessen der Code 2400 verwendet werden. + + + + + + Einrichtungen und Anlagen der Infrastruktur. + + + + + + Einrichtung der Post. + + + + + + Sonstige Anlage oder Einrichtung der Infrastruktur. + +Der Eintrag ist <b>veraltet </b>und wird in XPlanGML V. 6.0 entfernt. Es sollte stattdessen der Code 2600 verwendet werden. + + + + + + Sonstige Einrichtungen und Anlagen, die keiner anderen Kategorie zuzuordnen sind. + + + + + + + + + + Aufzählung der Zweckbestimmungen von Gewässern. + + + + + + Hafen + + + + + + Sportboothafen + + + + + + Stehende Wasserfläche, auch See, Teich. + + + + + + Fließgewässer, auch Fluss, Bach + + + + + + Sonstiges Gewässer, sofern keiner der anderen Codes zutreffend ist. + + + + + + + + + + + + + Parkanlage; auch: Erholungsgrün, Grünanlage, Naherholung. + + + + + + Historische Parkanlage + + + + + + Naturnahe Parkanlage + + + + + + Parkanlage mit Waldcharakter + + + + + + Ufernahe Parkanlage + + + + + + Dauerkleingarten; auch: Gartenfläche, Hofgärten, Gartenland. + + + + + + Erholungsgarten + + + + + + Sportplatz + + + + + + Reitsportanlage + + + + + + Hundesportanlage + + + + + + Wassersportanlage + + + + + + Schießstand + + + + + + Golfplatz + + + + + + Anlage für Skisport + + + + + + Tennisanlage + + + + + + Sonstiger Sportplatz + +Der Eintrag ist <b>veraltet </b>und wird in XPlanGML V. 6.0 entfernt. Es sollte stattdessen der Code 1400 verwendet werden. + + + + + + Spielplatz + + + + + + Bolzplatz + + + + + + Abenteuerspielplatz + + + + + + Zeltplatz + + + + + + Campingplatz + + + + + + Badeplatz, auch Schwimmbad, Liegewiese. + + + + + + Anlage für Freizeit und Erholung. + + + + + + Anlage für Kleintierhaltung + + + + + + Festplatz + + + + + + Spezielle Grünfläche + + + + + + Straßenbegleitgrün + + + + + + Böschungsfläche + + + + + + Feld, Wald, Wiese allgemein + +Der Eintrag ist <b>veraltet </b>und wird in XPlanGML V. 6.0 entfernt. Es sollte stattdessen der Code 2400 verwendet werden. + + + + + + Uferstreifen + + + + + + Abschirmgrün + + + + + + Umweltbildungspark, Schaugatter + + + + + + Fläche für den ruhenden Verkehr. + + + + + + Friedhof + + + + + + Sonstige Zweckbestimmung, falls keine der aufgeführten Klassifikationen anwendbar ist. + + + + + + Gärtnerei + + + + + + + + + + Aufzählung der Kennzeichnungen nach §5, Abs. 3 BauGB + + + + + + Flächen, bei deren Bebauung besondere bauliche Sicherungsmaßnahmen gegen Naturgewalten erforderlich sind (§5, Abs. 3, Nr. 1 BauGB). + + + + + + Flächen, die für den Abbau von Mineralien bestimmt sind (§5, Abs. 3, Nr. 2 und §9, Abs. 5, Nr. 2. BauGB). + + + + + + Flächen, bei deren Bebauung besondere bauliche Sicherungsmaßnahmen gegen äußere Einwirkungen erforderlich sind (§5, Abs. 3, Nr. 1 BauGB). + + + + + + Für bauliche Nutzung vorgesehene Flächen, deren Böden erheblich mit umweltgefährdenden Stoffen belastet sind (§5, Abs. 3, Nr. 3 BauGB). + + + + + + Für bauliche Nutzung vorgesehene Flächen, die erheblicher Lärmbelastung ausgesetzt sind. + + + + + + Flächen, unter denen der Bergbau umgeht (§5, Abs. 3, Nr. 2 und §9, Abs. 5, Nr. 2. BauGB). + + + + + + <font color="#009900">Für Bodenordnungsmaßnahmen vorgesehene Gebiete, </font> +<font color="#009900">z.B. Gebiete für Umlegungen oder Flurbereinigung</font> + + + + + + Räumlich besonders gekennzeichnetes Vorhabengebiets, das kleiner als der Geltungsbereich ist, innerhalb eines vorhabenbezogenen BPlans. + + + + + + Kennzeichnung nach anderen gesetzlichen Vorschriften. + + + + + + + + + + + + + Allgemeine Landwirtschaft + + + + + + Ackerbau + + + + + + Wiesen- und Weidewirtschaft + + + + + + Gartenbauliche Erzeugung + + + + + + Obstbau + + + + + + Weinbau + + + + + + Imkerei + + + + + + Binnenfischerei + + + + + + Sonstiges + + + + + + + + + + + + + Sportanlage + + + + + + Spielanlage + + + + + + Spiel- und/oder Sportanlage. + + + + + + Sonstiges + + + + + + + + + + + + + Elektrizität allgemein + + + + + + Hochspannungsleitung + + + + + + Trafostation, auch Umspannwerk + + + + + + Solarkraftwerk + + + + + + Windkraftwerk, Windenergieanlage, Windrad. + + + + + + Geothermie Kraftwerk + + + + + + Elektrizitätswerk allgemein + + + + + + Wasserkraftwerk + + + + + + Biomasse-Kraftwerk + + + + + + Kabelleitung + + + + + + Niederspannungsleitung + + + + + + Leitungsmast + + + + + + Kernkraftwerk + + + + + + Kohlekraftwerk + + + + + + Gaskraftwerk + + + + + + Gas allgemein + + + + + + Ferngasleitung + + + + + + Gaswerk + + + + + + Gasbehälter + + + + + + Gasdruckregler + + + + + + Gasstation + + + + + + Gasleitung + + + + + + Erdöl allgemein + + + + + + Erdölleitung + + + + + + Bohrstelle + + + + + + Erdölpumpstation + + + + + + Öltank + + + + + + Wärmeversorgung allgemein + + + + + + Blockheizkraftwerk + + + + + + Fernwärmeleitung + + + + + + Fernheizwerk + + + + + + Trink- und Brauchwasser allgemein + + + + + + Wasserwerk + + + + + + Trinkwasserleitung + + + + + + Wasserspeicher + + + + + + Brunnen + + + + + + Pumpwerk + + + + + + Quelle + + + + + + Abwasser allgemein + + + + + + Abwasserleitung + + + + + + Abwasserrückhaltebecken + + + + + + Abwasserpumpwerk, auch Abwasserhebeanlage + + + + + + Kläranlage + + + + + + Anlage zur Speicherung oder Behandlung von Klärschlamm. + + + + + + Sonstige Abwasser-Behandlungsanlage. + +Der Eintrag ist <b>veraltet </b>und wird in XPlanGML V. 6.0 entfernt. Es sollte stattdessen der Code 1800 verwendet werden. + + + + + + Salz- oder Sole-Leitungen + + + + + + Regenwasser allgemein + + + + + + Regenwasser Rückhaltebecken + + + + + + Niederschlagswasser-Leitung + + + + + + Abfallentsorgung allgemein + + + + + + Müll-Umladestation + + + + + + Müllbeseitigungsanlage + + + + + + Müllsortieranlage + + + + + + Recyclinghof + + + + + + Ablagerung allgemein + + + + + + Erdaushub-Deponie + + + + + + Bauschutt-Deponie + + + + + + Hausmüll-Deponie + + + + + + Sondermüll-Deponie + + + + + + Stillgelegte Deponie + + + + + + Rekultivierte Deponie + + + + + + Telekommunikation allgemein + + + + + + Fernmeldeanlage + + + + + + Mobilfunkanlage + + + + + + Fernmeldekabel + + + + + + Erneuerbare Energien allgemein + + + + + + Fläche oder Anlage für Kraft-Wärme Kopplung + + + + + + Sonstige, durch keinen anderen Code abbildbare Ver- oder Entsorgungsfläche bzw. -Anlage. + + + + + + Produktenleitung + + + + + + + + + + + + + Naturwald + + + + + + Waldschutzgebiet + + + + + + Nutzwald + + + + + + Erholungswald + + + + + + Schutzwald + + + + + + Bodenschutzwald + + + + + + Biotopschutzwald + + + + + + Naturnaher Wald + + + + + + Wald zum Schutz vor schädlichen Umwelteinwirkungen + + + + + + Schonwald + + + + + + Bannwald + + + + + + Fläche für die Forstwirtschaft. + + + + + + Immissionsgeschädigter Wald + + + + + + Sonstigr Wald + + + + + + + + + + Aufzählung wasserwirtschaftlicher Zweckbestimmungen. + + + + + + Hochwasser-Rückhaltebecken + + + + + + Überschwemmungsgefährdetes Gebiet nach §31c des vor dem 1.10.2010 gültigen WHG + + + + + + Versickerungsfläche + + + + + + Entwässerungsgraben + + + + + + Deich + + + + + + Regen-Rückhaltebecken + + + + + + Sonstige Wasserwirtschaftsfläche, sofern keiner der anderen Codes zutreffend ist. + + + + + + + + + + + Abstrakte Basisklasse für alle Präsentationsobjekte. Die Attribute entsprechen dem ALKIS-Objekt AP_GPO, wobei das Attribut "signaturnummer" in <i>stylesheetId </i>umbenannt wurde. Bei freien Präsentationsobjekten ist die Relation "<i>dientZurDarstellungVon</i>" unbelegt, bei gebundenen Präsentationsobjekten zeigt die Relation auf ein von <i>XP_Objekt </i>abgeleitetes Fachobjekt. +Freie Präsentationsobjekte dürfen <b>ausschließlich </b>zur graphischen Annotation eines Plans verwendet werden +Gebundene Präsentationsobjekte mit Raumbezug dienen <b>ausschließlich </b>dazu, Attributwerte des verbundenen Fachobjekts im Plan darzustellen. Die Namen der darzustellenden Fachobjekt-Attribute werden über das Attribut "<i>art</i>" spezifiziert. Bei mehrfach belegbaren Attributen in Fachobjekten gibt <i>index </i>die Position des Attributwertes an, auf den sich das Präsentationsobjekt bezieht. + + + + + + + + xplan:XP_StylesheetListe + + + + + + + + + + xplan:XP_Bereich + xplan:praesentationsobjekt + + + + + + + xplan:XP_Objekt + xplan:wirdDargestelltDurch + + + + + + + + + + + + + + + + Flächenförmiges Präsentationsobjekt. Entspricht der ALKIS Objektklasse AP_FPO. + + + + + + + + + + + + + + + + + + Enumeration der definierten horizontalen Fontausrichtungen + + + + + + Text linksbündig am Textpunkt bzw. am ersten Punkt der Linie. + + + + + Text rechtsbündig am Textpunkt bzw. am letzten Punkt der Linie. + + + + + Text zentriert am Textpunkt bzw. in der Mitte der Textstandlinie. + + + + + + + + + + + Linienförmiges Präsentationsobjekt Entspricht der ALKIS Objektklasse AP_LPO. + + + + + + + + + + + + + + + + + + + Textförmiges Präsentationsobjekt mit linienförmiger Textgeometrie. Entspricht der ALKIS-Objektklasse AP_LTO. + + + + + + + + + + + + + + + + + + + Modelliert eine Nutzungsschablone. Die darzustellenden Attributwerte werden zeilenweise in die Nutzungsschablone geschrieben. + + + + + + + + + + + + + + + + + + + + Punktförmiges Präsentationsobjekt. Entspricht der ALKIS-Objektklasse AP_PPO. + + + + + + + + + + + + xplan:XP_LPO + + + + + + + + + + + + + + + + Entspricht der ALKIS-Objektklasse AP_Darstellung mit dem Unterschied, dass auf das Attribut "positionierungssregel" verzichtet wurde. Die Klasse darf nur als gebundenes Präsentationsobjekt verwendet werden. Die Standard-Darstellung des verbundenen Fachobjekts wird dann durch die über stylesheetId spezifizierte Darstellung ersetzt. Die Umsetzung dieses Konzeptes ist der Implementierung überlassen. + + + + + + + + + + + + + + + + + + Textförmiges Präsentationsobjekt mit punktförmiger Festlegung der Textposition. Entspricht der ALKIS-Objektklasse AP_PTO. + + + + + + + + + + + + + + + + + + + + Abstrakte Oberklasse für textliche Präsentationsobjekte. Entspricht der ALKIS Objektklasse AP_TPO + + + + + + + + + + + + + xplan:XP_LPO + + + + + + + + + + + + + + + Enumeration der definierten vertikalen Fontausrichtungen + + + + + + Textgeometrie bezieht sich auf die Basis- bzw. Grundlinie der Buchstaben. + + + + + Textgeometrie bezieht sich auf die Mittellinie der Buchstaben. + + + + + Textgeometrie bezieht sich auf die Oberlinie der Großbuchstaben. + + + + + + + + + + + Georeferenzierte Rasterdarstellung eines Plans. Das über <i>refScan </i>referierte Rasterbild zeigt den Basisplan, dessen Geltungsbereich durch den Geltungsbereich des Gesamtplans (Attribut <i>geltungsbereich </i>von <i>XP_Plan</i>) repräsentiert ist. + +Im Standard sind nur georeferenzierte Rasterpläne zugelassen. Die über <i>refScan </i>referierte externe Referenz muss deshalb entweder vom Typ "<i>PlanMitGeoreferenz</i>" sein oder einen WMS-Request enthalten. + +Die Klasse ist <b>veraltet </b>und wird in XPlanGML V. 6.0 eliminiert. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/xsd/XPlanGML_FPlan.xsd b/tests/xsd/XPlanGML_FPlan.xsd new file mode 100644 index 0000000..ceeafb4 --- /dev/null +++ b/tests/xsd/XPlanGML_FPlan.xsd @@ -0,0 +1,1495 @@ + + + + + + + + + + + + Diese Klasse modelliert einen Bereich eines Flächennutzungsplans. + + + + + + + + + + + + + + xplan:FP_Plan + xplan:bereich + + + + + + + + + + + + + + + + Basisklasse für alle Objekte eines Flächennutzungsplans mit flächenhaftem Raumbezug (eine Einzelfläche oder eine Menge von Flächen, die sich nicht überlappen dürfen). Die von <i>FP_Flaechenobjekt </i>abgeleiteten Fachobjekte können sowohl als Flächenschlussobjekte als auch als Überlagerungsobjekte auftreten. + + + + + + + + + + + + + + + + + + + Basisklasse für alle Objekte eines Flächennutzungsplans mit flächenhaftem Raumbezug, die auf Ebene 0 immer Flächenschlussobjekte sind. +Flächenschlussobjekte dürfen sich nicht überlappen, sondern nur an den Flächenrändern berühren, wobei die jeweiligen Stützpunkte der Randkurven übereinander liegen müssen. Die Vereinigung der Flächenschlussobjekte überdeckt den Geltungsbereich des Flächennutzungsplans vollständig. + + + + + + + + + + + + + + + + + Basisklasse für alle Objekte eines Flächennutzungsplans mit variablem Raumbezug. Ein konkretes Objekt muss entweder punktförmigen, linienförmigen oder flächenhaften Raumbezug haben, gemischte Geometrie ist nicht zugelassen. + + + + + + + + + + + + + + + + + + + + + Basisklasse für alle Objekte eines Flächennutzungsplans mit linienförmigem Raumbezug (eine einzelne zusammenhängende Kurve, die aus Linienstücken und Kreisbögen zusammengesetzt sein kann, oder eine Menge derartiger Kurven). + + + + + + + + + + + + + + + + + + + Basisklasse für alle Fachobjekte des Flächennutzungsplans. + + + + + + + + + xplan:FP_SpezifischePraegungTypen + + + + + + + + xplan:XP_TextAbschnitt + + + + + + + xplan:FP_AusgleichsFlaeche + + + + + + + xplan:FP_SchutzPflegeEntwicklung + + + + + + + + + + + + + + + + Klasse zur Modellierung eines gesamten Flächennutzungsplans. + + + + + + + + + + + + xplan:FP_SonstPlanArt + + + + + + + + + + xplan:FP_Status + + + + + + + + + + + + + + + + + + + + + + xplan:FP_Bereich + xplan:gehoertZuPlan + + + + + + + + + + + + + + + Aufzählung verschiedener Arten von Flächennutzungsplänen + + + + + + Flächennutzungsplan nach § 5 BauGB. + + + + + + Gemeinsamer Flächennutzungsplan nach § 204 BauGB + + + + + + Regionaler Flächennutzungsplan, der zugleich die Funktion eines Regionalplans als auch eines gemeinsamen Flächennutzungsplans nach § 204 BauGB erfüllt + + + + + + Flächennutzungsplan mit regionalplanerischen Festlegungen (nur in HH, HB, B). + + + + + + Sachlicher Teilflächennutzungsplan nach §5 Abs. 2b BauGB. + + + + + + Sonstiger Flächennutzungsplan + + + + + + + + + + + + Basisklasse für alle Objekte eines Flächennutzungsplans mit punktförmigem Raumbezug (Einzelpunkt oder Punktmenge). + + + + + + + + + + + + + + + + + + + + + Darstellung im Flächennutzungsplan + + + + + + Nachrichtliche Übernahme aus anderen Planwerken. + + + + + + Hinweis nach BauGB + + + + + + Vermerk nach §9 BauGB + + + + + + Kennzeichnung nach §5 Abs. (3) BauGB. + + + + + + Der Rechtscharakter des FPlan-Inhaltes ist unbekannt. + + + + + + + + + + + + + Der Aufstellungsbeschluss liegt vor. + + + + + + Ein Planentwurf liegt vor. + + + + + + Die frühzeitige Bürgerbeteiligung ist abgeschlossen. + + + + + + Die frühzeitige Beteiligun der Öffentlichkeit ist abgeschlossen. + + + + + + Die Behördenbeteiligung ist abgeschlossen. + + + + + + Die öffentliche Auslegung ist beendet. + + + + + + Der Plan ist technisch erstellt worden. + + + + + + Der Plan ist rechtswirksam. + + + + + + Der Plan wurde außer Kraft gesetzt + + + + + + Der Plan wurde durch ein förmliches Verfahren aufgehoben + + + + + + Der Plan ist ohne förmliches Verfahren z.B. durch Überplanung außer Kraft getreten + + + + + + + + + + + Texlich formulierter Inhalt eines Flächennutzungsplans, der einen anderen Rechtscharakter als das zugrunde liegende Fachobjekt hat (Attribut <i>rechtscharakter </i>des Fachobjektes), oder dem Plan als Ganzes zugeordnet ist. + + + + + + + + + + + + + + + + + + Basisklasse für alle Objekte eines Flächennutzungsplans mit flächenhaftem Raumbezug, die immer Überlagerungsobjekte sind. + + + + + + + + + + + + + + + + + + + Normales FPlan Verfahren. + + + + + + FPlan Verfahren nach Parag 13 BauGB. + + + + + + + + + + + Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen (§5, Abs. 2, Nr. 8 BauGB). Hier: Flächen für Abgrabungen und die Gewinnung von Bodenschätzen. + + + + + + + + + + + + + + + + + + Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen (§5, Abs. 2, Nr. 8 BauGB). Hier: Flächen für Aufschüttungen. + + + + + + + + + + + + + + + + + + Flächen für Aufschüttungen, Abgrabungen oder für die Gewinnung von Bodenschätzen (§5, Abs. 2, Nr. 8 BauGB. Hier: Flächen für Bodenschätze. + +Die Klasse wird als <b>veraltet </b>gekennzeichnet und wird in XPlanGML V. 6.0 wegfallen. Es sollte stattdessen die Klasse F<i>P_Abgrabung </i>verwendet werden. + + + + + + + + + + + + + + + + + + Darstellung einer für die Bebauung vorgesehenen Fläche (§ 5, Abs. 2, Nr. 1 BauGB). + + + + + + + + + + + + + + + + xplan:FP_DetailArtDerBaulNutzung + + + + + + + xplan:FP_DetailSondernutzung + + + + + + + + + + + + + + + + + Baufläche, für die eine zentrale Abwasserbeseitigung nicht vorgesehen ist (§ 5, Abs. 2, Nr. 1 BauGB). + + + + + + + + + + + + + + + + + Anlagen, Einrichtungen und sonstige Maßnahmen, die der Anpassung an den Klimawandel dienen <u>nach §5 Abs.2 Nr.2c BauGB.</u> + + + + + + + + + xplan:FP_DetailMassnahmeKlimawandel + + + + + + + + + + + + + + + + Darstellung von Flächen für den Gemeinbedarf nach § 5, Abs. 2, Nr. 2 BauGB. + + + + + + + + + xplan:FP_DetailZweckbestGemeinbedarf + + + + + + + + + + + + + + + + + + Erhalt vegetationsbestandener Freiflächen + + + + + + Erhalt privater Grünflächen + + + + + + Erhalt öffentlicher Grünflächen + + + + + + Sonstige Massnahme + + + + + + + + + + + Darstellung von Flächen für Spiel- und Sportanlagen nach §5, Abs. 2, Nr. 2 BauGB. + + + + + + + + + xplan:FP_DetailZweckbestSpielSportanlage + + + + + + + + + + + + + + + + Darstellung einer Grünfläche nach § 5, Abs. 2, Nr. 5 BauGB, + + + + + + + + + xplan:FP_DetailZweckbestGruen + + + + + + + + + + + + + + + + + Darstellung einer Landwirtschaftsfläche nach §5, Abs. 2, Nr. 9a. + + + + + + + + + xplan:FP_DetailZweckbestLandwirtschaftsFlaeche + + + + + + + + + + + + + + + + Darstellung einer Landwirtschaftsfläche nach §5, Abs. 2, Nr. 9a. + +Die Klasse ist als veraltet gekennzeinet und wird in Version 6.0 wegfallen. Es sollte stattdessen die Klasse FP_Landwirtschaft verwendet werden. + + + + + + + + + xplan:FP_DetailZweckbestLandwirtschaftsFlaeche + + + + + + + + + + + + + + + + Darstellung von Waldflächen nach §5, Abs. 2, Nr. 9b, + + + + + + + + + xplan:FP_DetailZweckbestWaldFlaeche + + + + + + + + + + + + + + + + + + Flächen und Maßnahmen zum Ausgleich gemäß § 5, Abs. 2a BauBG. + + + + + + + + + + + + + + + + + + + + + + Umgrenzung von Flächen für Maßnahmen zum Schutz, zur Pflege und zur Entwicklung von Natur und Landschaft (§5 Abs. 2, Nr. 10 BauGB) + + + + + + + + + + + + + + + + + + + + + Inhalt des Flächennutzungsplans, der auf einer spezifischen Rechtsverordnung eines Bundeslandes beruht. + + + + + + + + xplan:FP_DetailZweckbestimmungNachLandesrecht + + + + + + + + + + + + + + + + + Fläche, für die keine geplante Nutzung angegben werden kann + + + + + + + + + + + + + + + + + Klasse zur Modellierung aller Inhalte des FPlans, die durch keine spezifische XPlanung-Klasse repräsentiert werden können. + + + + + + + + xplan:FP_ZweckbestimmungGenerischeObjekte + + + + + + + + + + + + + + + + Kennzeichnung gemäß §5 Abs. 3 BauGB. + + + + + + + + + + + + + + + + + + + + Umgrenzungen der Flächen für besondere Anlagen und Vorkehrungen zum Schutz vor schädlichen Umwelteinwirkungen im Sinne des Bundes- +Immissionsschutzgesetzes (§ 5, Abs. 2, Nr. 6 BauGB) + + + + + + + + + + + + + + + + + + Standorte für privilegierte Außenbereichsvorhaben und für sonstige Anlagen in Außenbereichen gem. § 35 Abs. 1 und 2 BauGB. + + + + + + + + + + + + + + + + + + + Bereich, in dem bestimmte Textliche Darstellungen gültig sind, die über die Relation "<i>refTextInhalt</i>" (Basisklasse <i>FP_Objekt</i>) spezifiziert werden. + + + + + + + + + + + + + + + + + Unverbindliche Vormerkung späterer Planungsabsichten + + + + + + + + + + + + + + + + + + + Flächen auf denen bestimmte Vorbehalte wirksam sind. + + + + + + + + + + + + + + + + + + + + Allgemeines Vorhaben nach §35 Abs. 1 Nr. 1 oder 2 BauGB: Vorhaben, dass "einem land- oder forstwirtschaftlichen Betrieb dient und nur einen untergeordneten Teil der Betriebsfläche einnimmt", oder "einem Betrieb der gartenbaulichen Erzeugung dient". + + + + + + Aussiedlerhof + + + + + + Altenteil + + + + + + Reiterhof + + + + + + Gartenbaubetrieb + + + + + + Baumschule + + + + + + Allgemeines Vorhaben nach § 35 Abs. 1 Nr. 3 BauBG: Vorhaben dass "der öffentlichen Versorgung mit Elektrizität, Gas, +Telekommunikationsdienstleistungen, Wärme und Wasser, der Abwasserwirtschaft" ... dient. + + + + + + Öffentliche Wasserversorgung + + + + + + Gasversorgung + + + + + + Versorgung mit Fernwärme + + + + + + Versorgung mit Elektrizität. + + + + + + Versorgung mit Telekommunikations-Dienstleistungen. + + + + + + Abwasser Entsorgung + + + + + + Vorhaben nach §35 Abs. 1 Nr. 3 BauGB: Vorhaben das ...."einem ortsgebundenen gewerblichen Betrieb dient". + + + + + + Vorhaben nach §35 Abs. 1 Nr. 4 BauGB: Vorhaben, dass "wegen seiner besonderen Anforderungen an die Umgebung, wegen seiner nachteiligen Wirkung auf die Umgebung oder wegen seiner besonderen Zweckbestimmung nur im Außenbereich ausgeführt werden soll". + + + + + + Vorhaben dass wegen seiner besonderen Anforderungen an die Umgebung nur im Außenbereich durchgeführt werden soll. + + + + + + Vorhaben dass wegen seiner nachteiligen Wirkung auf die Umgebung nur im Außenbereich durchgeführt werden soll. + + + + + + Vorhaben dass wegen seiner besonderen Zweckbestimmung nur im Außenbereich durchgeführt werden soll. + + + + + + Allgemeine Vorhaben nach §35 Abs. 1 Nr. 4 BauGB: Vorhaben, dass "wegen seiner besonderen Anforderungen an die Umgebung, wegen seiner nachteiligen Wirkung auf die Umgebung oder wegen seiner besonderen Zweckbestimmung nur im Außenbereich ausgeführt werden soll". + + + + + + Vorhaben zur Erforschung, Entwicklung oder Nutzung der Windenergie. + + + + + + Vorhaben zur Erforschung, Entwicklung oder Nutzung der Wasserenergie. + + + + + + Vorhaben zur Erforschung, Entwicklung oder Nutzung der Solarenergie. + + + + + + Vorhaben zur energetischen Nutzung der Biomasse. + + + + + + Vorhaben nach §35 Abs. 1 Nr. 7 BauGB: Vorhaben das "der Erforschung, Entwicklung oder Nutzung der Kernenergie zu friedlichen Zwecken oder der Entsorgung radioaktiver Abfälle dient". + + + + + + Vorhaben der Erforschung, Entwicklung oder Nutzung der Kernenergie zu friedlichen Zwecken. + + + + + + Vorhaben zur Entsorgung radioaktiver Abfälle. + + + + + + Sonstiges Vorhaben im Aussenbereich nach §35 Abs. 2 BauGB. + + + + + + Einzelhof + + + + + + Bebaute Fläche im Außenbereich + + + + + + + + + + + Flächen für Versorgungsanlagen, für die Abfallentsorgung und Abwasserbeseitigung sowie für Ablagerungen (§5, Abs. 2, Nr. 4 BauGB). + + + + + + + + + xplan:FP_DetailZweckbestVerEntsorgung + + + + + + + + + + + + + + + + + + Darstellung nach § 5 Abs. 2 Nr. 2d (Ausstattung des Gemeindegebietes mit zentralen Versorgungsbereichen). + + + + + + + + + xplan:FP_ZentralerVersorgungsbereichAuspraegung + + + + + + + + + + + + + + + + Darstellung von Flächen für den überörtlichen Verkehr und für die örtlichen Hauptverkehrszüge ( §5, Abs. 2, Nr. 3 BauGB). + + + + + + + + + xplan:FP_DetailZweckbestStrassenverkehr + + + + + + + + + + + + + + + + + + + Autobahn und autobahnähnliche Straße. + + + + + + Sonstige örtliche oder überörtliche Hauptverkehrsstraße bzw. Weg. + + + + + + Ortsdurchfahrt + + + + + + Sonstiger Verkehrsweg oder Anlage. + + + + + + Verkehrsberuhigter Bereich + + + + + + Platz + + + + + + Fußgängerbereich + + + + + + Rad- und Fußweg + + + + + + Radweg + + + + + + Fußweg + + + + + + Wanderweg + + + + + + Reit- und Kutschweg + + + + + + Rastanlage + + + + + + Busbahnhof, auch zentraler Omnibusbahnhof (ZOB) + + + + + + Brückenbereich, hier: Überführender Verkehrsweg. + + + + + + Brückenbereich, hier: Unterführender Verkehrsweg. + + + + + + Wirtschaftsweg + + + + + + Landwirtschaftlicher Verkehr + + + + + + Fläche oder Anlage für den ruhenden Verkehr + + + + + + Parkplatz + + + + + + Abstellplatz für Fahräder + + + + + + Park- and Ride-Anlage + + + + + + Fläche zum Car-Sharing + + + + + + Fläche zum Abstellen gemeinschaftlich genutzter Fahrräder + + + + + + Bike and Ride Anlage + + + + + + Parkhaus + + + + + + Mischverkehrsfläche + + + + + + Ladestation für Elektrofahrzeuge + + + + + + Sonstige Zweckbestimmung + + + + + + + + + + + Darstellung von Wasserflächen nach §5, Abs. 2, Nr. 7 BauGB. +Diese Klasse wird in der nächsten Hauptversion des Standards eventuell wegfallen und durch SO_Gewaesser ersetzt werden. + + + + + + + + + xplan:FP_DetailZweckbestGewaesser + + + + + + + + + + + + + + + + Die für die Wasserwirtschaft vorgesehenen Flächen sowie Flächen, die im Interesse des Hochwasserschutzes und der Regelung des Wasserabflusses freizuhalten sind (§5 Abs. 2 Nr. 7 BauGB). + + + + + + + + + xplan:FP_DetailZweckbestWasserwirtschaft + + + + + + + + + + \ No newline at end of file diff --git a/tests/xsd/XPlanung-Operationen.xsd b/tests/xsd/XPlanung-Operationen.xsd new file mode 100644 index 0000000..64e1cbc --- /dev/null +++ b/tests/xsd/XPlanung-Operationen.xsd @@ -0,0 +1,26 @@ + + + + + + + + + + + + + Container für XPlanGML-Objekte + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/xsd/gmlProfile/gmlProfilexplan.xsd b/tests/xsd/gmlProfile/gmlProfilexplan.xsd new file mode 100644 index 0000000..1ab4594 --- /dev/null +++ b/tests/xsd/gmlProfile/gmlProfilexplan.xsd @@ -0,0 +1,880 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + XLink components are the standard method to support hypertext referencing in XML. An XML Schema attribute group, gml:AssociationAttributeGroup, is provided to support the use of Xlinks as the method for indicating the value of a property by reference in a uniform manner in GML. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + deprecated + + + + + + deprecated + + + + + + + + + + + + + deprecated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A gml:PolygonPatch is a surface patch that is defined by a set of boundary curves and an underlying surface to which these curves adhere. The curves shall be coplanar and the polygon uses planar interpolation in its interior. +interpolation is fixed to "planar", i.e. an interpolation shall return points on a single plane. The boundary of the patch shall be contained within that plane. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/xsd/gmlProfile/xlink.xsd b/tests/xsd/gmlProfile/xlink.xsd new file mode 100644 index 0000000..09f4f99 --- /dev/null +++ b/tests/xsd/gmlProfile/xlink.xsd @@ -0,0 +1,272 @@ + + + + + + + This schema document provides attribute declarations and +attribute group, complex type and simple type definitions which can be used in +the construction of user schemas to define the structure of particular linking +constructs, e.g. + + + + + + + ... + + ... + + + ... +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Intended for use as the type of user-declared elements to make them + simple links. + + + + + + + + + + + + + + + + + + + + + + + + + Intended for use as the type of user-declared elements to make them + extended links. + Note that the elements referenced in the content model are all abstract. + The intention is that by simply declaring elements with these as their + substitutionGroup, all the right things will happen. + + + + + + + + + + + + + + xml:lang is not required, but provides much of the + motivation for title elements in addition to attributes, and so + is provided here for convenience. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + label is not required, but locators have no particular + XLink function if they are not labeled. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + from and to have default behavior when values are missing + + + + + + + + + + + + + + + + + diff --git a/tests/xsd/gmlProfile/xml.xsd b/tests/xsd/gmlProfile/xml.xsd new file mode 100644 index 0000000..d662b42 --- /dev/null +++ b/tests/xsd/gmlProfile/xml.xsd @@ -0,0 +1,117 @@ + + + + + + + See http://www.w3.org/XML/1998/namespace.html and + http://www.w3.org/TR/REC-xml for information about this namespace. + + This schema document describes the XML namespace, in a form + suitable for import by other schema documents. + + Note that local names in this namespace are intended to be defined + only by the World Wide Web Consortium or its subgroups. The + following names are currently defined in this namespace and should + not be used with conflicting semantics by any Working Group, + specification, or document instance: + + base (as an attribute name): denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification. + + lang (as an attribute name): denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification. + + space (as an attribute name): denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification. + + Father (in any context at all): denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: + + In appreciation for his vision, leadership and dedication + the W3C XML Plenary on this 10th day of February, 2000 + reserves for Jon Bosak in perpetuity the XML name + xml:Father + + + + + This schema defines attributes and an attribute group + suitable for use by + schemas wishing to allow xml:base, xml:lang or xml:space attributes + on elements they define. + + To enable this, such a schema must import this schema + for the XML namespace, e.g. as follows: + <schema . . .> + . . . + <import namespace="http://www.w3.org/XML/1998/namespace" + schemaLocation="http://www.w3.org/2001/03/xml.xsd"/> + + Subsequently, qualified reference to any of the attributes + or the group defined below will have the desired effect, e.g. + + <type . . .> + . . . + <attributeGroup ref="xml:specialAttrs"/> + + will define a type which will schema-validate an instance + element with any of those attributes + + + + In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + http://www.w3.org/2001/03/xml.xsd. + At the date of issue it can also be found at + http://www.w3.org/2001/xml.xsd. + The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML Schema + itself. In other words, if the XML Schema namespace changes, the version + of this document at + http://www.w3.org/2001/xml.xsd will change + accordingly; the version at + http://www.w3.org/2001/03/xml.xsd will not change. + + + + + + In due course, we should install the relevant ISO 2- and 3-letter + codes as the enumerated possible values . . . + + + + + + + + + + + + + + + See http://www.w3.org/TR/xmlbase/ for + information about this attribute. + + + + + + + + + +