feat: Import WEA types from Excel and implement dynamic dropdown UI
Deploy Standortplaner / deploy (push) Successful in 19s Details

This commit is contained in:
Johannes Baumeister 2026-07-03 22:17:33 +02:00
parent 242cdaa400
commit a844d8a872
16 changed files with 643 additions and 43 deletions

Binary file not shown.

97
app.js
View File

@ -110,8 +110,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const btnDeleteWEA = document.getElementById('btnDeleteWEA'); const btnDeleteWEA = document.getElementById('btnDeleteWEA');
const editNr = document.getElementById('edit-wea-nr'); const editNr = document.getElementById('edit-wea-nr');
const editManufacturer = document.getElementById('edit-wea-manufacturer'); const editWeaSelector = document.getElementById('edit-wea-selector');
const editType = document.getElementById('edit-wea-type');
const editRd = document.getElementById('edit-wea-rd'); const editRd = document.getElementById('edit-wea-rd');
const editNh = document.getElementById('edit-wea-nh'); const editNh = document.getElementById('edit-wea-nh');
const editFr = document.getElementById('edit-wea-fr'); const editFr = document.getElementById('edit-wea-fr');
@ -127,8 +126,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const inputRotor = document.getElementById('rotorDiameter'); const inputRotor = document.getElementById('rotorDiameter');
const inputHub = document.getElementById('hubHeight'); const inputHub = document.getElementById('hubHeight');
const inputFoundation = document.getElementById('foundationRadius'); const inputFoundation = document.getElementById('foundationRadius');
const inputManufacturer = document.getElementById('turbineManufacturer'); const weaTypeSelector = document.getElementById('weaTypeSelector');
const inputType = document.getElementById('turbineType');
let placementMode = false; let placementMode = false;
let measureMode = null; // 'dist' or 'area' let measureMode = null; // 'dist' or 'area'
@ -291,8 +289,20 @@ document.addEventListener('DOMContentLoaded', async () => {
} }
activeTurbine = turbine; activeTurbine = turbine;
editNr.value = turbine.nr; editNr.value = turbine.nr;
editManufacturer.value = turbine.hersteller || 'Enercon'; let matched = false;
editType.value = turbine.type; for (const opt of editWeaSelector.options) {
if (opt.value) {
try {
const data = JSON.parse(opt.value);
if (data.hersteller === turbine.hersteller && data.anlagentyp === turbine.type && data.nabenhoehe === turbine.hh) {
editWeaSelector.value = opt.value;
matched = true;
break;
}
} catch(e) {}
}
}
if (!matched) editWeaSelector.value = "";
editRd.value = turbine.rd; editRd.value = turbine.rd;
editNh.value = turbine.hh; editNh.value = turbine.hh;
editFr.value = turbine.fr || 15; editFr.value = turbine.fr || 15;
@ -374,8 +384,15 @@ document.addEventListener('DOMContentLoaded', async () => {
const rd = overrideData?.rd || Number.parseFloat(inputRotor?.value) || 160; const rd = overrideData?.rd || Number.parseFloat(inputRotor?.value) || 160;
const hh = overrideData?.hh || Number.parseFloat(inputHub?.value) || 165; const hh = overrideData?.hh || Number.parseFloat(inputHub?.value) || 165;
const fr = overrideData?.fr || Number.parseFloat(inputFoundation?.value) || 15; const fr = overrideData?.fr || Number.parseFloat(inputFoundation?.value) || 15;
const type = overrideData?.type || inputType?.value || "Standard Typ"; let type = overrideData?.type || "Standard Typ";
const hersteller = overrideData?.hersteller || inputManufacturer?.value || "Enercon"; let hersteller = overrideData?.hersteller || "Unbekannt";
if (!overrideData && weaTypeSelector && weaTypeSelector.value) {
try {
const parsed = JSON.parse(weaTypeSelector.value);
type = parsed.anlagentyp;
hersteller = parsed.hersteller;
} catch(e) {}
}
const weaNr = overrideData?.nr || loadedNr || (state.turbines.length + 1).toString(); const weaNr = overrideData?.nr || loadedNr || (state.turbines.length + 1).toString();
const ksfAngle = overrideData?.ksfAngle || 0; const ksfAngle = overrideData?.ksfAngle || 0;
const ksfMirrored = overrideData?.ksfMirrored || false; const ksfMirrored = overrideData?.ksfMirrored || false;
@ -536,8 +553,15 @@ document.addEventListener('DOMContentLoaded', async () => {
btnSaveEdit.onclick = () => { btnSaveEdit.onclick = () => {
if (!activeTurbine) return; if (!activeTurbine) return;
const newNr = editNr.value; const newNr = editNr.value;
const newManufacturer = editManufacturer.value; let newManufacturer = activeTurbine.hersteller;
const newType = editType.value; let newType = activeTurbine.type;
if (editWeaSelector && editWeaSelector.value) {
try {
const parsed = JSON.parse(editWeaSelector.value);
newManufacturer = parsed.hersteller;
newType = parsed.anlagentyp;
} catch(e) {}
}
const newRd = Number.parseFloat(editRd.value); const newRd = Number.parseFloat(editRd.value);
const newNh = Number.parseFloat(editNh.value); const newNh = Number.parseFloat(editNh.value);
const newFr = Number.parseFloat(editFr.value) || 15; const newFr = Number.parseFloat(editFr.value) || 15;
@ -2164,6 +2188,58 @@ document.addEventListener('DOMContentLoaded', async () => {
} }
// Initialize allowed projects and project selector // Initialize allowed projects and project selector
window.availableWeaTypes = [];
async function loadWeaTypes() {
try {
const res = await fetch('/api/wea-types');
if (!res.ok) throw new Error('Network response was not ok');
window.availableWeaTypes = await res.json();
// Build grouped options
const groups = {};
window.availableWeaTypes.forEach(wea => {
if (!groups[wea.hersteller]) groups[wea.hersteller] = [];
groups[wea.hersteller].push(wea);
});
let html = '<option value="">Bitte Anlage wählen...</option>';
for (const h in groups) {
html += `<optgroup label="${h}">`;
groups[h].forEach(wea => {
const jsonStr = JSON.stringify(wea).replace(/"/g, '&quot;');
html += `<option value="${jsonStr}">${wea.anlagentyp} (NH: ${wea.nabenhoehe}m, RD: ${wea.rotordurchmesser}m, Nennleistung: ${wea.nennleistung}MW)</option>`;
});
html += `</optgroup>`;
}
if (weaTypeSelector) {
weaTypeSelector.innerHTML = html;
weaTypeSelector.addEventListener('change', (e) => {
if (!e.target.value) return;
try {
const data = JSON.parse(e.target.value.replace(/&quot;/g, '"'));
inputRotor.value = data.rotordurchmesser;
inputHub.value = data.nabenhoehe;
} catch(err) {}
});
}
if (editWeaSelector) {
editWeaSelector.innerHTML = html;
editWeaSelector.addEventListener('change', (e) => {
if (!e.target.value) return;
try {
const data = JSON.parse(e.target.value.replace(/&quot;/g, '"'));
editRd.value = data.rotordurchmesser;
editNh.value = data.nabenhoehe;
} catch(err) {}
});
}
} catch (err) {
console.error('Fehler beim Laden der Anlagentypen:', err);
}
}
async function initializeProjects() { async function initializeProjects() {
const statusEl = document.getElementById('statusInfo'); const statusEl = document.getElementById('statusInfo');
try { try {
@ -2258,6 +2334,7 @@ document.addEventListener('DOMContentLoaded', async () => {
} }
// Start loading process // Start loading process
await loadWeaTypes();
initializeProjects(); initializeProjects();
// Project Persistence // Project Persistence

97
append_pdf.js Normal file
View File

@ -0,0 +1,97 @@
const fs = require('fs');
const js = `
// --- PDF Export Logic ---
const btnExportPDF = document.getElementById('btnExportPDF');
if (btnExportPDF) {
btnExportPDF.addEventListener('click', async () => {
const originalText = btnExportPDF.innerText;
btnExportPDF.innerText = 'Exportiere...';
btnExportPDF.disabled = true;
try {
if (typeof html2canvas === 'undefined' || typeof jspdf === 'undefined') {
throw new Error('PDF Bibliotheken (html2canvas, jspdf) sind noch nicht geladen.');
}
const mapContainer = document.getElementById('map');
// Add Overlay
const exportOverlay = document.createElement('div');
exportOverlay.style.position = 'absolute';
exportOverlay.style.bottom = '30px';
exportOverlay.style.left = '30px';
exportOverlay.style.zIndex = '9999';
exportOverlay.style.background = 'rgba(255, 255, 255, 0.95)';
exportOverlay.style.padding = '15px';
exportOverlay.style.border = '2px solid #333';
exportOverlay.style.borderRadius = '5px';
exportOverlay.style.color = '#333';
exportOverlay.style.fontFamily = 'Arial, sans-serif';
exportOverlay.style.boxShadow = '0 0 10px rgba(0,0,0,0.3)';
const projName = document.getElementById('projectSelector')?.options[document.getElementById('projectSelector')?.selectedIndex]?.text || 'Unbekannt';
exportOverlay.innerHTML = \`
<div style="font-weight: bold; font-size: 1.4rem; margin-bottom: 8px; color: #004a99;">ENWELO Standortplanung</div>
<div style="font-size: 1rem; margin-bottom: 12px; font-weight: bold;">Projekt: \${projName}</div>
<div style="display: flex; flex-direction: column; gap: 6px; font-size: 0.9rem;">
<div style="display: flex; align-items: center; gap: 8px;"><div style="width: 16px; height: 16px; background: #00c8ff; border: 1px dashed #00c8ff; opacity: 0.5;"></div> Rotorüberstreichfläche</div>
<div style="display: flex; align-items: center; gap: 8px;"><div style="width: 16px; height: 16px; background: #00aaff; border: 1px dashed #00aaff; opacity: 0.5;"></div> Gebietsumring</div>
<div style="display: flex; align-items: center; gap: 8px;"><div style="width: 16px; height: 3px; background: #ff0000;"></div> Abstandslinien</div>
<div style="display: flex; align-items: center; gap: 8px;"><div style="width: 16px; height: 16px; border-radius: 50%; background: #00c8ff;"></div> WEA Standort</div>
</div>
\`;
mapContainer.appendChild(exportOverlay);
// Wait a tiny bit for DOM to render overlay
await new Promise(r => setTimeout(r, 100));
const canvas = await html2canvas(mapContainer, {
useCORS: true,
allowTaint: true,
ignoreElements: (element) => {
return element.classList.contains('leaflet-control-zoom');
}
});
mapContainer.removeChild(exportOverlay);
const imgData = canvas.toDataURL('image/png');
const pdf = new jspdf.jsPDF({
orientation: 'landscape',
unit: 'mm',
format: 'a3'
});
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const imgProps = pdf.getImageProperties(imgData);
const ratio = imgProps.width / imgProps.height;
let renderWidth = pdfWidth;
let renderHeight = pdfWidth / ratio;
if (renderHeight > pdfHeight) {
renderHeight = pdfHeight;
renderWidth = pdfHeight * ratio;
}
// Center image in PDF
const xOffset = (pdfWidth - renderWidth) / 2;
const yOffset = (pdfHeight - renderHeight) / 2;
pdf.addImage(imgData, 'PNG', xOffset, yOffset, renderWidth, renderHeight);
pdf.save('Karte_Enwelo.pdf');
} catch (err) {
console.error('Fehler beim PDF Export:', err);
alert('Fehler beim PDF Export: ' + err.message);
} finally {
btnExportPDF.innerText = originalText;
btnExportPDF.disabled = false;
}
});
}
`;
fs.appendFileSync('app.js', js);

30
db_add_column.js Normal file
View File

@ -0,0 +1,30 @@
const { Pool } = require('pg');
const pool = new Pool({
user: 'enwelo_admin',
host: '87.106.21.21',
database: 'enwelo',
password: 'WX1t1cgP1qK09',
port: 5432,
ssl: false
});
async function run() {
try {
await pool.query('ALTER TABLE geodaten."potentialflächen_wind" ADD COLUMN IF NOT EXISTS projekt_id VARCHAR(255);');
console.log('Column projekt_id added successfully.');
// Set for samern-ohne as default since they want to see it!
// But wait, what if there's only one row?
const res = await pool.query('SELECT id FROM geodaten."potentialflächen_wind"');
console.log('Found rows:', res.rowCount);
if (res.rowCount === 1) {
await pool.query("UPDATE geodaten.\"potentialflächen_wind\" SET projekt_id = 'projekt_bw_samern-ohne'");
console.log('Updated the single row to projekt_bw_samern-ohne');
}
} catch(e) {
console.error('Fehler:', e);
} finally {
await pool.end();
}
}
run();

25
db_add_planungsstand.js Normal file
View File

@ -0,0 +1,25 @@
const { Pool } = require('pg');
const pool = new Pool({
user: 'enwelo_admin',
host: '87.106.21.21',
database: 'enwelo',
password: 'WX1t1cgP1qK09',
port: 5432,
ssl: false
});
async function run() {
try {
await pool.query('ALTER TABLE geodaten."potentialflächen_wind" ADD COLUMN IF NOT EXISTS planungsstand VARCHAR(255);');
console.log('Column planungsstand added successfully.');
const res = await pool.query("UPDATE geodaten.\"potentialflächen_wind\" SET planungsstand = 'rechtsverbindlich'");
console.log(`Updated ${res.rowCount} rows to rechtsverbindlich`);
} catch(e) {
console.error('Fehler:', e);
} finally {
await pool.end();
}
}
run();

31
db_check_potential.js Normal file
View File

@ -0,0 +1,31 @@
const { Pool } = require('pg');
const pool = new Pool({
user: 'enwelo_admin',
host: '87.106.21.21',
database: 'enwelo',
password: 'WX1t1cgP1qK09',
port: 5432,
ssl: false
});
async function run() {
try {
const res = await pool.query("SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = 'geodaten' AND table_name = 'potentialflächen_wind';");
console.log('Columns:', res.rows);
// Check if projekt_id exists in columns
const hasProjektId = res.rows.some(r => r.column_name === 'projekt_id');
if (hasProjektId) {
const dataRes = await pool.query('SELECT DISTINCT projekt_id FROM geodaten."potentialflächen_wind";');
console.log('Projects:', dataRes.rows);
} else {
console.log('No projekt_id column found.');
}
} catch(e) {
console.error('Fehler:', e);
} finally {
await pool.end();
}
}
run();

View File

@ -0,0 +1,40 @@
const { Pool } = require('pg');
const pool = new Pool({
user: 'enwelo_admin',
host: '87.106.21.21',
database: 'enwelo',
password: 'WX1t1cgP1qK09',
port: 5432,
ssl: false
});
async function run() {
try {
const client = await pool.connect();
await client.query('BEGIN');
// Update artennachweise
let res = await client.query("UPDATE geodaten.artennachweise SET projekt_id = 'trasse_bw_scheddebrock' WHERE projekt_id = 'BW Scheddebrock'");
console.log('Artennachweise (trasse_bw_scheddebrock):', res.rowCount);
res = await client.query("UPDATE geodaten.artennachweise SET projekt_id = 'projekt_bw_samern-ohne' WHERE projekt_id = 'bw_samern-ohne'");
console.log('Artennachweise (projekt_bw_samern-ohne):', res.rowCount);
res = await client.query("UPDATE geodaten.artennachweise SET projekt_id = 'projekt_bw_test' WHERE projekt_id = 'test'");
console.log('Artennachweise (projekt_bw_test):', res.rowCount);
res = await client.query("UPDATE geodaten.artennachweise SET projekt_id = 'projekt_bw_an_der_landwehr' WHERE projekt_id ILIKE '%landwehr%'");
console.log('Artennachweise (projekt_bw_an_der_landwehr):', res.rowCount);
await client.query('COMMIT');
console.log('--- ERFOLGREICH GESPEICHERT ---');
client.release();
} catch(e) {
await pool.query('ROLLBACK');
console.error('Fehler:', e);
} finally {
await pool.end();
}
}
run();

54
db_update_temp.js Normal file
View File

@ -0,0 +1,54 @@
const { Pool } = require('pg');
const pool = new Pool({
user: 'enwelo_admin',
host: '87.106.21.21',
database: 'enwelo',
password: 'WX1t1cgP1qK09',
port: 5432,
ssl: false
});
async function run() {
try {
const client = await pool.connect();
await client.query('BEGIN');
// 1. Projekte
let res = await client.query("UPDATE geodaten.projekte SET name = 'trasse_bw_scheddebrock' WHERE name = 'BW Scheddebrock'");
console.log('Projekte (trasse_bw_scheddebrock):', res.rowCount);
res = await client.query("UPDATE geodaten.projekte SET name = 'projekt_bw_samern-ohne' WHERE name = 'bw_samern-ohne'");
console.log('Projekte (projekt_bw_samern-ohne):', res.rowCount);
res = await client.query("UPDATE geodaten.projekte SET name = 'projekt_bw_test' WHERE name = 'test'");
console.log('Projekte (projekt_bw_test):', res.rowCount);
res = await client.query("UPDATE geodaten.projekte SET name = 'projekt_bw_an_der_landwehr' WHERE name ILIKE '%landwehr%'");
console.log('Projekte (projekt_bw_an_der_landwehr):', res.rowCount);
// 2. WEA Standorte
res = await client.query("UPDATE geodaten.wea_standorte SET projekt_id = 'trasse_bw_scheddebrock' WHERE projekt_id = 'BW Scheddebrock'");
console.log('WEA (trasse_bw_scheddebrock):', res.rowCount);
res = await client.query("UPDATE geodaten.wea_standorte SET projekt_id = 'projekt_bw_samern-ohne' WHERE projekt_id = 'bw_samern-ohne'");
console.log('WEA (projekt_bw_samern-ohne):', res.rowCount);
res = await client.query("UPDATE geodaten.wea_standorte SET projekt_id = 'projekt_bw_test' WHERE projekt_id = 'test'");
console.log('WEA (projekt_bw_test):', res.rowCount);
res = await client.query("UPDATE geodaten.wea_standorte SET projekt_id = 'projekt_bw_an_der_landwehr' WHERE projekt_id ILIKE '%landwehr%'");
console.log('WEA (projekt_bw_an_der_landwehr):', res.rowCount);
await client.query('COMMIT');
console.log('--- ERFOLGREICH GESPEICHERT ---');
client.release();
} catch(e) {
await pool.query('ROLLBACK');
console.error('Fehler:', e);
} finally {
await pool.end();
}
}
run();

View File

@ -0,0 +1,11 @@
-- 1. Aktualisieren der Haupttabelle "projekte"
UPDATE geodaten.projekte SET name = 'trasse_bw_scheddebrock' WHERE name = 'BW Scheddebrock';
UPDATE geodaten.projekte SET name = 'projekt_bw_samern-ohne' WHERE name = 'bw_samern-ohne';
UPDATE geodaten.projekte SET name = 'projekt_bw_test' WHERE name = 'test';
UPDATE geodaten.projekte SET name = 'projekt_bw_an_der_landwehr' WHERE name ILIKE '%landwehr%';
-- 2. Aktualisieren der Verknüpfungen in den Standorten (WEA)
UPDATE geodaten.wea_standorte SET projekt_id = 'trasse_bw_scheddebrock' WHERE projekt_id = 'BW Scheddebrock';
UPDATE geodaten.wea_standorte SET projekt_id = 'projekt_bw_samern-ohne' WHERE projekt_id = 'bw_samern-ohne';
UPDATE geodaten.wea_standorte SET projekt_id = 'projekt_bw_test' WHERE projekt_id = 'test';
UPDATE geodaten.wea_standorte SET projekt_id = 'projekt_bw_an_der_landwehr' WHERE projekt_id ILIKE '%landwehr%';

61
import_umring.js Normal file
View File

@ -0,0 +1,61 @@
const shapefile = require('shapefile');
const { Pool } = require('pg');
const pool = new Pool({
user: 'enwelo_admin',
host: '87.106.21.21',
database: 'enwelo',
password: 'WX1t1cgP1qK09',
port: 5432,
ssl: false
});
async function run() {
try {
console.log("Reading shapefile...");
const geojson = await shapefile.read("Shapefile/Windeignungsgebiet.shp", "Shapefile/Windeignungsgebiet.dbf");
console.log(`Loaded ${geojson.features.length} features.`);
if (geojson.features.length === 0) {
console.log("No features found!");
return;
}
const client = await pool.connect();
try {
await client.query('BEGIN');
// wir wollen die Shapefile-Geometrie für samern-ohne in die DB pushen
const projektId = 'projekt_bw_samern-ohne';
// Zuerst bestehende löschen?
await client.query('DELETE FROM geodaten."potentialflächen_wind" WHERE projekt_id = $1', [projektId]);
let count = 0;
for (const feature of geojson.features) {
const geomJson = JSON.stringify(feature.geometry);
await client.query(
`INSERT INTO geodaten."potentialflächen_wind" (projekt_id, geom)
VALUES ($1, ST_SetSRID(ST_GeomFromGeoJSON($2), 25832))`,
[projektId, geomJson]
);
count++;
}
await client.query('COMMIT');
console.log(`Erfolgreich ${count} Potentialflächen für ${projektId} importiert!`);
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}
} catch(e) {
console.error("Fehler:", e);
} finally {
await pool.end();
}
}
run();

64
import_wea.js Normal file
View File

@ -0,0 +1,64 @@
require('dotenv').config({ override: true });
const { Pool } = require('pg');
const xlsx = require('xlsx');
const pool = new Pool({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD
});
async function importWea() {
const wb = xlsx.readFile('./Anlagentypen/Anlagentypen.xlsx');
const sheet = wb.Sheets[wb.SheetNames[0]];
const data = xlsx.utils.sheet_to_json(sheet);
try {
console.log('Connecting to DB...');
await pool.query('BEGIN');
console.log('Dropping and creating table geodaten.anlagendaten_wea...');
await pool.query(`
DROP TABLE IF EXISTS geodaten.anlagendaten_wea;
CREATE TABLE geodaten.anlagendaten_wea (
id SERIAL PRIMARY KEY,
hersteller VARCHAR(255),
anlagentyp VARCHAR(255),
nennleistung FLOAT,
nabenhoehe FLOAT,
rotordurchmesser FLOAT,
gesamthoehe FLOAT
);
`);
console.log(`Importing ${data.length} rows...`);
for (const row of data) {
// Keys from Excel
// "Hersteller", "Anlagentyp", "Nennleistung (MW)", "Nabenhöhe (m)", "Rotordurchmesser (m)", "Gesamthöhe (m)"
const hersteller = row['Hersteller'] || '';
const anlagentyp = row['Anlagentyp'] || '';
const nennleistung = parseFloat(row['Nennleistung (MW)'] || 0);
const nabenhoehe = parseFloat(row['Nabenhöhe (m)'] || 0);
const rotordurchmesser = parseFloat(row['Rotordurchmesser (m)'] || 0);
const gesamthoehe = parseFloat(row['Gesamthöhe (m)'] || 0);
await pool.query(`
INSERT INTO geodaten.anlagendaten_wea
(hersteller, anlagentyp, nennleistung, nabenhoehe, rotordurchmesser, gesamthoehe)
VALUES ($1, $2, $3, $4, $5, $6)
`, [hersteller, anlagentyp, nennleistung, nabenhoehe, rotordurchmesser, gesamthoehe]);
}
await pool.query('COMMIT');
console.log('Import finished successfully.');
} catch (err) {
await pool.query('ROLLBACK');
console.error('Error importing:', err);
} finally {
await pool.end();
}
}
importWea();

View File

@ -74,28 +74,19 @@
WEA Konfiguration <span style="margin-left: auto;">+</span> WEA Konfiguration <span style="margin-left: auto;">+</span>
</summary> </summary>
<div style="margin-top: 12px; cursor: default;"> <div style="margin-top: 12px; cursor: default;">
<div style="display: flex; gap: 8px; margin-bottom: 6px;"> <div style="margin-bottom: 8px;">
<div style="flex: 1;"> <label for="weaTypeSelector" class="label-small">Anlagentyp (aus Datenbank)</label>
<label for="turbineManufacturer" class="label-small">Hersteller</label> <select id="weaTypeSelector" class="input-styled input-small" style="width: 100%;">
<select id="turbineManufacturer" class="input-styled input-small"> <option value="">Lade Anlagentypen...</option>
<option value="Enercon">Enercon</option>
<option value="Nordex">Nordex</option>
<option value="Vestas">Vestas</option>
<option value="GE">GE</option>
</select> </select>
</div> </div>
<div style="flex: 1;"> <div style="display: flex; gap: 8px; align-items: center; margin-bottom: 6px;">
<label for="turbineType" class="label-small">Anlagentyp</label> <label for="rotorDiameter" class="label-small" style="flex: 1; margin: 0; color: var(--text-dim);">Rotordurchmesser (m)</label>
<input type="text" id="turbineType" class="input-styled input-small" placeholder="z.B. V162"> <input type="number" id="rotorDiameter" class="input-styled input-small" value="" style="width: 60px; background: rgba(255,255,255,0.05);" readonly>
</div>
</div> </div>
<div style="display: flex; gap: 8px; align-items: center; margin-bottom: 6px;"> <div style="display: flex; gap: 8px; align-items: center; margin-bottom: 6px;">
<label for="rotorDiameter" class="label-small" style="flex: 1; margin: 0;">Rotordurchmesser (m)</label> <label for="hubHeight" class="label-small" style="flex: 1; margin: 0; color: var(--text-dim);">Nabenhöhe (m)</label>
<input type="number" id="rotorDiameter" class="input-styled input-small" value="160" style="width: 60px;"> <input type="number" id="hubHeight" class="input-styled input-small" value="" style="width: 60px; background: rgba(255,255,255,0.05);" readonly>
</div>
<div style="display: flex; gap: 8px; align-items: center; margin-bottom: 6px;">
<label for="hubHeight" class="label-small" style="flex: 1; margin: 0;">Nabenhöhe (m)</label>
<input type="number" id="hubHeight" class="input-styled input-small" value="165" style="width: 60px;">
</div> </div>
<div style="display: flex; gap: 8px; align-items: center; margin-bottom: 6px;"> <div style="display: flex; gap: 8px; align-items: center; margin-bottom: 6px;">
<label for="foundationRadius" class="label-small" style="flex: 1; margin: 0;">Fundament Radius (m)</label> <label for="foundationRadius" class="label-small" style="flex: 1; margin: 0;">Fundament Radius (m)</label>
@ -180,26 +171,19 @@
<label for="edit-wea-nr">Nr:</label> <label for="edit-wea-nr">Nr:</label>
<input type="text" id="edit-wea-nr" class="edit-input"> <input type="text" id="edit-wea-nr" class="edit-input">
</div> </div>
<div class="edit-row separator"> <div class="edit-row separator" style="flex-direction: column; align-items: flex-start; gap: 4px;">
<label for="edit-wea-manufacturer">Hersteller:</label> <label for="edit-wea-selector">Anlagentyp:</label>
<select id="edit-wea-manufacturer" class="edit-input"> <select id="edit-wea-selector" class="edit-input" style="width: 100%;">
<option value="Enercon">Enercon</option> <option value="">Lade Anlagentypen...</option>
<option value="Nordex">Nordex</option>
<option value="Vestas">Vestas</option>
<option value="GE">GE</option>
</select> </select>
</div> </div>
<div class="edit-row separator">
<label for="edit-wea-type">Typ:</label>
<input type="text" id="edit-wea-type" class="edit-input">
</div>
<div class="edit-row"> <div class="edit-row">
<label for="edit-wea-rd">RD (m):</label> <label for="edit-wea-rd">RD (m):</label>
<input type="number" id="edit-wea-rd" class="edit-input"> <input type="number" id="edit-wea-rd" class="edit-input" style="background: rgba(255,255,255,0.05);" readonly>
</div> </div>
<div class="edit-row separator"> <div class="edit-row separator">
<label for="edit-wea-nh">NH (m):</label> <label for="edit-wea-nh">NH (m):</label>
<input type="number" id="edit-wea-nh" class="edit-input"> <input type="number" id="edit-wea-nh" class="edit-input" style="background: rgba(255,255,255,0.05);" readonly>
</div> </div>
<div class="edit-row"> <div class="edit-row">
<label for="edit-wea-fr">Fund (m):</label> <label for="edit-wea-fr">Fund (m):</label>

106
package-lock.json generated
View File

@ -13,7 +13,8 @@
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.19.2",
"pg": "^8.11.5", "pg": "^8.11.5",
"shapefile": "^0.6.6" "shapefile": "^0.6.6",
"xlsx": "^0.18.5"
} }
}, },
"node_modules/accepts": { "node_modules/accepts": {
@ -29,6 +30,15 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/adler-32": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/array-flatten": { "node_modules/array-flatten": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@ -103,6 +113,28 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/cfb": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
"license": "Apache-2.0",
"dependencies": {
"adler-32": "~1.3.0",
"crc-32": "~1.2.0"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/codepage": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/commander": { "node_modules/commander": {
"version": "2.20.3", "version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@ -162,6 +194,18 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"license": "Apache-2.0",
"bin": {
"crc32": "bin/crc32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -358,6 +402,15 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/frac": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/fresh": { "node_modules/fresh": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@ -1001,6 +1054,18 @@
"node": ">= 10.x" "node": ">= 10.x"
} }
}, },
"node_modules/ssf": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
"license": "Apache-2.0",
"dependencies": {
"frac": "~1.1.2"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@ -1072,6 +1137,45 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/wmf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/word": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/xlsx": {
"version": "0.18.5",
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
"license": "Apache-2.0",
"dependencies": {
"adler-32": "~1.3.0",
"cfb": "~1.2.1",
"codepage": "~1.15.0",
"crc-32": "~1.2.1",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
},
"bin": {
"xlsx": "bin/xlsx.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/xtend": { "node_modules/xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@ -8,7 +8,8 @@
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.19.2",
"pg": "^8.11.5", "pg": "^8.11.5",
"shapefile": "^0.6.6" "shapefile": "^0.6.6",
"xlsx": "^0.18.5"
}, },
"scripts": { "scripts": {
"start": "node server.js" "start": "node server.js"

7
read_excel.js Normal file
View File

@ -0,0 +1,7 @@
const xlsx = require('xlsx');
const wb = xlsx.readFile('./Anlagentypen/Anlagentypen.xlsx');
const sheetName = wb.SheetNames[0];
const sheet = wb.Sheets[sheetName];
const data = xlsx.utils.sheet_to_json(sheet);
console.log('Columns:', Object.keys(data[0] || {}));
console.log('Sample Data:', data.slice(0, 3));

View File

@ -215,6 +215,20 @@ app.get('/api/logs', (req, res) => {
res.type('text/plain').send(serverLogs.join('\n')); res.type('text/plain').send(serverLogs.join('\n'));
}); });
// API to load available WEA types
app.get('/api/wea-types', async (req, res) => {
const client = await pool.connect();
try {
const result = await client.query('SELECT * FROM geodaten.anlagendaten_wea ORDER BY hersteller, anlagentyp, nabenhoehe');
res.status(200).json(result.rows);
} catch (e) {
log(`FEHLER beim Laden der Anlagentypen: ${e.message}`);
res.status(500).json({ error: e.message });
} finally {
client.release();
}
});
// API to save turbines // API to save turbines
app.post('/api/wea', async (req, res) => { app.post('/api/wea', async (req, res) => {
const { projekt_id, turbines } = req.body; const { projekt_id, turbines } = req.body;