diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index b37a05b..6a090ec 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -12,33 +12,16 @@ jobs: - name: Code holen uses: actions/checkout@v4 - - name: Dateien auf Server kopieren - uses: appleboy/scp-action@master - with: - host: ${{ secrets.SERVER_HOST }} - username: ${{ secrets.SERVER_USER }} - key: ${{ secrets.SSH_PRIVATE_KEY }} - source: "." - target: "/opt/automation-stack/standortplaner" + - name: .env erstellen + run: | + echo "DB_HOST=${{ secrets.DB_HOST }}" > .env + echo "DB_PORT=${{ secrets.DB_PORT }}" >> .env + echo "DB_USER=${{ secrets.DB_USER }}" >> .env + echo "DB_PASSWORD=${{ secrets.DB_PASSWORD }}" >> .env + echo "DB_NAME=${{ secrets.DB_NAME }}" >> .env + echo "OIDC_CLIENT_ID=${{ secrets.OIDC_CLIENT_ID }}" >> .env + echo "OIDC_CLIENT_SECRET=${{ secrets.OIDC_CLIENT_SECRET }}" >> .env - - name: Deployment auf Server ausführen - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.SERVER_HOST }} - username: ${{ secrets.SERVER_USER }} - key: ${{ secrets.SSH_PRIVATE_KEY }} - script: | - cd /opt/automation-stack/standortplaner - - # .env Datei mit den Secrets befüllen - echo "DB_HOST=${{ secrets.DB_HOST }}" > .env - echo "DB_PORT=${{ secrets.DB_PORT }}" >> .env - echo "DB_USER=${{ secrets.DB_USER }}" >> .env - echo "DB_PASSWORD=${{ secrets.DB_PASSWORD }}" >> .env - echo "DB_NAME=${{ secrets.DB_NAME }}" >> .env - echo "DB_SCHEMA=geodaten" >> .env - echo "OIDC_CLIENT_ID=${{ secrets.OIDC_CLIENT_ID }}" >> .env - echo "OIDC_CLIENT_SECRET=${{ secrets.OIDC_CLIENT_SECRET }}" >> .env - - # App starten - docker compose up -d --build --force-recreate \ No newline at end of file + - name: App bauen und starten + run: | + docker compose up -d --build --force-recreate \ No newline at end of file diff --git a/Shapefile/Test/Flurstücke.cpg b/Shapefile/Test/Flurstücke.cpg new file mode 100644 index 0000000..3ad133c --- /dev/null +++ b/Shapefile/Test/Flurstücke.cpg @@ -0,0 +1 @@ +UTF-8 \ No newline at end of file diff --git a/Shapefile/Test/Flurstücke.dbf b/Shapefile/Test/Flurstücke.dbf new file mode 100644 index 0000000..cf997e4 Binary files /dev/null and b/Shapefile/Test/Flurstücke.dbf differ diff --git a/Shapefile/Test/Flurstücke.prj b/Shapefile/Test/Flurstücke.prj new file mode 100644 index 0000000..bd846ae --- /dev/null +++ b/Shapefile/Test/Flurstücke.prj @@ -0,0 +1 @@ +PROJCS["ETRS_1989_UTM_Zone_32N",GEOGCS["GCS_ETRS_1989",DATUM["D_ETRS_1989",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",9.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]] \ No newline at end of file diff --git a/Shapefile/Test/Flurstücke.qmd b/Shapefile/Test/Flurstücke.qmd new file mode 100644 index 0000000..2c146d7 --- /dev/null +++ b/Shapefile/Test/Flurstücke.qmd @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs + 0 + 0 + + + + + false + + + + + + + + + + + + diff --git a/Shapefile/Test/Flurstücke.shp b/Shapefile/Test/Flurstücke.shp new file mode 100644 index 0000000..f31923c Binary files /dev/null and b/Shapefile/Test/Flurstücke.shp differ diff --git a/Shapefile/Test/Flurstücke.shx b/Shapefile/Test/Flurstücke.shx new file mode 100644 index 0000000..0ec4b63 Binary files /dev/null and b/Shapefile/Test/Flurstücke.shx differ diff --git a/Shapefile/Test/Wohngebäude.cpg b/Shapefile/Test/Wohngebäude.cpg new file mode 100644 index 0000000..3ad133c --- /dev/null +++ b/Shapefile/Test/Wohngebäude.cpg @@ -0,0 +1 @@ +UTF-8 \ No newline at end of file diff --git a/Shapefile/Test/Wohngebäude.dbf b/Shapefile/Test/Wohngebäude.dbf new file mode 100644 index 0000000..ad40aa8 Binary files /dev/null and b/Shapefile/Test/Wohngebäude.dbf differ diff --git a/Shapefile/Test/Wohngebäude.prj b/Shapefile/Test/Wohngebäude.prj new file mode 100644 index 0000000..bd846ae --- /dev/null +++ b/Shapefile/Test/Wohngebäude.prj @@ -0,0 +1 @@ +PROJCS["ETRS_1989_UTM_Zone_32N",GEOGCS["GCS_ETRS_1989",DATUM["D_ETRS_1989",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",9.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]] \ No newline at end of file diff --git a/Shapefile/Test/Wohngebäude.qmd b/Shapefile/Test/Wohngebäude.qmd new file mode 100644 index 0000000..7387d45 --- /dev/null +++ b/Shapefile/Test/Wohngebäude.qmd @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + 0 + 0 + + + + + false + + + + diff --git a/Shapefile/Test/Wohngebäude.shp b/Shapefile/Test/Wohngebäude.shp new file mode 100644 index 0000000..dd6c6a6 Binary files /dev/null and b/Shapefile/Test/Wohngebäude.shp differ diff --git a/Shapefile/Test/Wohngebäude.shx b/Shapefile/Test/Wohngebäude.shx new file mode 100644 index 0000000..af35020 Binary files /dev/null and b/Shapefile/Test/Wohngebäude.shx differ diff --git a/app.js b/app.js index 1f279e5..062c99c 100644 --- a/app.js +++ b/app.js @@ -1326,37 +1326,24 @@ document.addEventListener('DOMContentLoaded', async () => { console.warn("Leitungsverläufe-Layer konnte nicht geladen werden."); } - // Gebäude laden - console.log("Lade Gebäude aus Datenbank..."); + // Wohngebäude laden + console.log("Lade Wohngebäude aus Datenbank..."); const gebaudeResp = await fetch('/api/layers/gebaeude').catch(err => { - console.error("Netzwerkfehler beim Laden der Gebäude:", err); + console.error("Netzwerkfehler beim Laden der Wohngebäude:", err); return null; }); if (gebaudeResp?.ok) { const data = await gebaudeResp.json(); - console.log(`Gebäude API: ${data.features ? data.features.length : 0} Features erhalten.`); - if (data.features && data.features.length > 0) { - const layerName = "Gebäude (DB)"; - const gebaudeLayer = L.geoJSON(data, { - style: { - color: "gray", - weight: 1, - fillColor: "gray", - fillOpacity: 0.5 - } - }); - - if (overlays[layerName]) { - state.map.removeLayer(overlays[layerName]); - layerControl.removeLayer(overlays[layerName]); - } - overlays[layerName] = gebaudeLayer; - state.map.addLayer(gebaudeLayer); - layerControl.addOverlay(gebaudeLayer, layerName); - } + console.log(`Wohngebäude API: ${data.features ? data.features.length : 0} Features erhalten.`); + createDbLayer(data, "Wohngebäude (DB)", () => ({ + color: "red", + weight: 1, + fillColor: "red", + fillOpacity: 0.4 + }), false); } else { - console.warn("Gebäude-Layer konnte nicht geladen werden."); + console.warn("Wohngebäude-Layer konnte nicht geladen werden."); } statusEl.innerText = "Layer geladen (ALKIS DB, Artennachweise, Leitungen & Gebäude integriert)."; diff --git a/import_test_data.js b/import_test_data.js new file mode 100644 index 0000000..90b8dc8 --- /dev/null +++ b/import_test_data.js @@ -0,0 +1,180 @@ +const { Client } = require('pg'); +const shapefile = require('shapefile'); +const crypto = require('node:crypto'); + +const firstNames = ['Hans', 'Peter', 'Thomas', 'Michael', 'Andreas', 'Wolfgang', 'Klaus', 'Jürgen', 'Günter', 'Christian', 'Maria', 'Ursula', 'Monika', 'Petra', 'Elisabeth', 'Sabine', 'Renate', 'Karin', 'Helga', 'Gisela']; +const lastNames = ['Müller', 'Schmidt', 'Schneider', 'Fischer', 'Weber', 'Meyer', 'Wagner', 'Becker', 'Schulz', 'Hoffmann', 'Schäfer', 'Koch', 'Bauer', 'Richter', 'Klein', 'Wolf', 'Schröder', 'Neumann', 'Schwarz', 'Zimmermann']; + +function getRandomElement(arr) { + return arr[Math.floor(Math.random() * arr.length)]; +} + +function getRandomStatus() { + const val = Math.random(); + if (val < 0.1) return 'Gesichert'; + if (val < 0.3) return 'In Verhandlung'; + return 'Offen'; +} + +function geojsonToWkt(geom) { + if (geom.type === 'Polygon') { + const rings = geom.coordinates.map(ring => + '(' + ring.map(pt => `${pt[0]} ${pt[1]}`).join(', ') + ')' + ).join(', '); + return `MULTIPOLYGON((${rings}))`; + } else if (geom.type === 'MultiPolygon') { + const polys = geom.coordinates.map(poly => + '(' + poly.map(ring => + '(' + ring.map(pt => `${pt[0]} ${pt[1]}`).join(', ') + ')' + ).join(', ') + ')' + ).join(', '); + return `MULTIPOLYGON(${polys})`; + } else if (geom.type === 'Point') { + return `POINT(${geom.coordinates[0]} ${geom.coordinates[1]})`; + } + throw new Error('Unsupported geometry type: ' + geom.type); +} + +async function run() { + const client = new Client({ + host: '87.106.21.21', + port: 5432, + user: 'enwelo_admin', + password: 'WX1t1cgP1qK09', + database: 'enwelo' + }); + + try { + await client.connect(); + console.log("Connected to database."); + + // 1. Ensure project 'test' exists + let projectUuid; + const checkProj = await client.query("SELECT id FROM geodaten.projekte WHERE name = 'test';"); + if (checkProj.rows.length > 0) { + projectUuid = checkProj.rows[0].id; + console.log(`Project 'test' already exists with UUID: ${projectUuid}`); + } else { + projectUuid = crypto.randomUUID(); + await client.query("INSERT INTO geodaten.projekte (id, name, beschreibung) VALUES ($1, $2, $3);", [projectUuid, 'test', 'Testprojekt für Dummydaten']); + console.log(`Created project 'test' with UUID: ${projectUuid}`); + } + + // 2. Update turbine where project_id IS NULL to 'test' + console.log("Updating wind turbine..."); + const weaUpdate = await client.query( + `UPDATE geodaten.wea_standorte + SET projekt_id = 'test', wea_nummer = COALESCE(wea_nummer, '1') + WHERE projekt_id IS NULL OR projekt_id = '' + RETURNING id, ST_AsText(geom) as geom_wkt;` + ); + console.log(`Updated ${weaUpdate.rowCount} wind turbine(s) to project 'test'.`); + + // 3. Import Flurstücke + console.log("Reading Flurstücke shapefile..."); + const flurstuecke = await shapefile.read("Shapefile/Test/Flurstücke.shp"); + console.log(`Found ${flurstuecke.features.length} flurstücke features.`); + + let insertedAlkis = 0; + let insertedZuweisung = 0; + let insertedStatus = 0; + + for (let i = 0; i < flurstuecke.features.length; i++) { + const f = flurstuecke.features[i]; + const props = f.properties; + const fsk = props.flstkennz || props.FSK; + if (!fsk) continue; + + const wkt = geojsonToWkt(f.geometry); + + // Check if ALKIS owner entry already exists + const checkAlkis = await client.query('SELECT 1 FROM geodaten.flaecheneigentuemer_alkis WHERE "FSK" = $1;', [fsk]); + if (checkAlkis.rows.length === 0) { + const gna = getRandomElement(lastNames); + const vna = getRandomElement(firstNames); + const gemarkung = props.gemarkung || props.GMK__BEZ || ''; + const flur = parseInt(props.flur || props.FLN) || null; + const gemeinde = props.gemeinde || props.GEM__BEZ || ''; + + await client.query( + `INSERT INTO geodaten.flaecheneigentuemer_alkis ("FSK", "GNA", "VNA", "GMK__BEZ", "FLN", "GEM__BEZ", geom) + VALUES ($1, $2, $3, $4, $5, $6, ST_SetSRID(ST_GeomFromText($7), 25832));`, + [fsk, gna, vna, gemarkung, flur, gemeinde, wkt] + ); + insertedAlkis++; + } + + // Check if project assignment exists + const checkZuw = await client.query('SELECT 1 FROM geodaten.flaecheneigentuemer_alkis_zuweisung WHERE fsk = $1 AND projekt_id = $2;', [fsk, projectUuid]); + if (checkZuw.rows.length === 0) { + await client.query( + `INSERT INTO geodaten.flaecheneigentuemer_alkis_zuweisung (id, fsk, projekt_id, zugewiesen_am) + VALUES ($1, $2, $3, NOW());`, + [crypto.randomUUID(), fsk, projectUuid] + ); + insertedZuweisung++; + } + + // Check if status entry exists + const checkStat = await client.query('SELECT 1 FROM geodaten.flaecheneigentuemer_status WHERE fsk = $1 AND projekt_id = $2;', [fsk, projectUuid]); + if (checkStat.rows.length === 0) { + const status = getRandomStatus(); + await client.query( + `INSERT INTO geodaten.flaecheneigentuemer_status (id, fsk, projekt_id, status, datum) + VALUES ($1, $2, $3, $4, NOW());`, + [crypto.randomUUID(), fsk, projectUuid, status] + ); + insertedStatus++; + } + + if ((i + 1) % 200 === 0) { + console.log(`Processed ${i + 1} / ${flurstuecke.features.length} flurstücke...`); + } + } + console.log(`Flurstücke summary: Inserted ${insertedAlkis} owners, ${insertedZuweisung} assignments, ${insertedStatus} status values.`); + + // 4. Import Wohngebäude + console.log("Reading Wohngebäude shapefile..."); + const wohngebaeude = await shapefile.read("Shapefile/Test/Wohngebäude.shp"); + console.log(`Found ${wohngebaeude.features.length} wohngebäude features.`); + + let insertedWohn = 0; + for (let i = 0; i < wohngebaeude.features.length; i++) { + const f = wohngebaeude.features[i]; + const props = f.properties; + const wkt = geojsonToWkt(f.geometry); + + const oi = props.OI || ''; + const ags = props.AGS || ''; + const gfk = props.GFK || ''; + const aktualitaet = props.AKTUALITAE || props.AKTUALITÄT || ''; + const wert = props.WERT !== undefined ? parseInt(props.WERT) : null; + const axGeb = props.AX_Gebäude !== undefined ? parseInt(props.AX_Gebäude) : (props.AX_Gebäud !== undefined ? parseInt(props.AX_Gebäud) : null); + const randPoint = props.rand_point !== undefined ? parseInt(props.rand_point) : null; + + // Check if building already exists based on geometry/identifier (or just insert since it's dummy data, but let's check OI to avoid duplicates) + const checkWohn = await client.query('SELECT 1 FROM geodaten.wohngebeaude WHERE "OI" = $1;', [oi]); + if (checkWohn.rows.length === 0) { + await client.query( + `INSERT INTO geodaten.wohngebeaude (geom, rand_point_id, "AGS", "OI", "GFK", "AKTUALITAE", "WERT", "AX_Gebäude") + VALUES (ST_SetSRID(ST_GeomFromText($1), 25832), $2, $3, $4, $5, $6, $7, $8);`, + [wkt, randPoint, ags, oi, gfk, aktualitaet, wert, axGeb] + ); + insertedWohn++; + } + + if ((i + 1) % 500 === 0) { + console.log(`Processed ${i + 1} / ${wohngebaeude.features.length} wohngebäude...`); + } + } + console.log(`Wohngebäude summary: Inserted ${insertedWohn} new residential buildings.`); + + console.log("Successfully completed data import!"); + + } catch (e) { + console.error("Database transaction error:", e); + } finally { + await client.end(); + } +} +run(); diff --git a/package-lock.json b/package-lock.json index 3a926a3..d235529 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", - "pg": "^8.11.5" + "pg": "^8.11.5", + "shapefile": "^0.6.6" } }, "node_modules/accepts": { @@ -34,6 +35,12 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/array-source": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/array-source/-/array-source-0.0.4.tgz", + "integrity": "sha512-frNdc+zBn80vipY+GdcJkLEbMWj3xmzArYApmUGxoiV8uAu/ygcs9icPdsGdA26h0MkHUMW6EN2piIvVx+M5Mw==", + "license": "BSD-3-Clause" + }, "node_modules/body-parser": { "version": "1.20.5", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", @@ -96,6 +103,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -309,6 +322,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/file-source": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-source/-/file-source-0.6.1.tgz", + "integrity": "sha512-1R1KneL7eTXmXfKxC10V/9NeGOdbsAXJ+lQ//fvvcHUgtaZcZDWNJNblxAoVOyV1cj45pOtUrR3vZTBwqcW8XA==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-source": "0.3" + } + }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", @@ -600,6 +622,16 @@ "node": ">= 0.8" } }, + "node_modules/path-source": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/path-source/-/path-source-0.1.3.tgz", + "integrity": "sha512-dWRHm5mIw5kw0cs3QZLNmpUWty48f5+5v9nWD2dw3Y0Hf+s01Ag8iJEWV0Sm0kocE8kK27DrIowha03e1YR+Qw==", + "license": "BSD-3-Clause", + "dependencies": { + "array-source": "0.0", + "file-source": "0.6" + } + }, "node_modules/path-to-regexp": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", @@ -864,6 +896,24 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/shapefile": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/shapefile/-/shapefile-0.6.6.tgz", + "integrity": "sha512-rLGSWeK2ufzCVx05wYd+xrWnOOdSV7xNUW5/XFgx3Bc02hBkpMlrd2F1dDII7/jhWzv0MSyBFh5uJIy9hLdfuw==", + "license": "BSD-3-Clause", + "dependencies": { + "array-source": "0.0", + "commander": "2", + "path-source": "0.1", + "slice-source": "0.4", + "stream-source": "0.3", + "text-encoding": "^0.6.4" + }, + "bin": { + "dbf2json": "bin/dbf2json", + "shp2json": "bin/shp2json" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -936,6 +986,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/slice-source": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/slice-source/-/slice-source-0.4.1.tgz", + "integrity": "sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==", + "license": "BSD-3-Clause" + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -954,6 +1010,19 @@ "node": ">= 0.8" } }, + "node_modules/stream-source": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/stream-source/-/stream-source-0.3.5.tgz", + "integrity": "sha512-ZuEDP9sgjiAwUVoDModftG0JtYiLUV8K4ljYD1VyUMRWtbVf92474o4kuuul43iZ8t/hRuiDAx1dIJSvirrK/g==", + "license": "BSD-3-Clause" + }, + "node_modules/text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", + "deprecated": "no longer maintained", + "license": "Unlicense" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", diff --git a/package.json b/package.json index 64326c4..00c2005 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", - "pg": "^8.11.5" + "pg": "^8.11.5", + "shapefile": "^0.6.6" }, "scripts": { "start": "node server.js" diff --git a/server.js b/server.js index f41bce4..1eb514b 100644 --- a/server.js +++ b/server.js @@ -338,7 +338,7 @@ app.get('/api/layers/leitungsverlaeufe', async (req, res) => { // NEU: API zum Laden der Gebäude app.get('/api/layers/gebaeude', async (req, res) => { - log("Lade Gebäude aus Datenbank..."); + log("Lade Wohngebäude aus Datenbank..."); try { const result = await pool.query( `SELECT jsonb_build_object( @@ -351,12 +351,12 @@ app.get('/api/layers/gebaeude', async (req, res) => { 'geometry', ST_AsGeoJSON(ST_Transform(geom, 4326))::jsonb, 'properties', jsonb_build_object() ) AS feature - FROM geodaten.gebaeude + FROM geodaten.wohngebeaude ) features` ); res.json(result.rows[0].jsonb_build_object); } catch (err) { - log(`FEHLER beim Laden der Gebäude: ${err.message}`); + log(`FEHLER beim Laden der Wohngebäude: ${err.message}`); res.status(500).json({ error: err.message }); } });