fix: JSON.parse of wea selector values (handle "); ensure mw is parsed correctly
Deploy Standortplaner / deploy (push) Successful in 17s
Details
Deploy Standortplaner / deploy (push) Successful in 17s
Details
This commit is contained in:
parent
3ce303a00f
commit
f8732b91d7
624
app.js
624
app.js
|
|
@ -371,6 +371,201 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
btnToggleLegend.innerHTML = floatingLegend.classList.contains('collapsed') ? '▼' : '▲';
|
btnToggleLegend.innerHTML = floatingLegend.classList.contains('collapsed') ? '▼' : '▲';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
state.map.on('overlayadd overlayremove', updateLegend);
|
||||||
|
// Also update on variant change
|
||||||
|
variantTabs.forEach(tab => {
|
||||||
|
tab.addEventListener('click', () => {
|
||||||
|
// ... existing variant logic (already there, but we need to trigger updateLegend)
|
||||||
|
setTimeout(updateLegend, 100);
|
||||||
|
});
|
||||||
|
// We'll use negative angle for CCW to achieve CW rotation if expected.
|
||||||
|
|
||||||
|
const transform = (relCoords) => {
|
||||||
|
// Manual Cartesian Rotation (Clockwise)
|
||||||
|
// x' = x * cos(a) + y * sin(a)
|
||||||
|
// y' = -x * sin(a) + y * cos(a)
|
||||||
|
|
||||||
|
const a = (ksfAngle * Math.PI) / 180;
|
||||||
|
const cosA = Math.cos(a);
|
||||||
|
const sinA = Math.sin(a);
|
||||||
|
|
||||||
|
const finalCoords = relCoords.map(c => {
|
||||||
|
const x = c[0] * spg;
|
||||||
|
const y = c[1];
|
||||||
|
|
||||||
|
// Rotation CW
|
||||||
|
const xRot = x * cosA + y * sinA;
|
||||||
|
const yRot = -x * sinA + y * cosA;
|
||||||
|
|
||||||
|
const utmPoint = [centerE + xRot, centerN + yRot];
|
||||||
|
return proj4(utm32, wgs84, utmPoint);
|
||||||
|
});
|
||||||
|
|
||||||
|
return turf.polygon([finalCoords]);
|
||||||
|
};
|
||||||
|
|
||||||
|
let blfCoords, ksfCoords, mfParts;
|
||||||
|
|
||||||
|
if (hersteller?.toLowerCase() === 'nordex') {
|
||||||
|
// Nordex Geometries (Based on new QGIS schema provided by user)
|
||||||
|
// KSF (Red): Complex polygon
|
||||||
|
ksfCoords = [
|
||||||
|
[18, -44.72], [18, 14.91], [-6.31, 14.91], [-18.38, 0],
|
||||||
|
[-18.38, -33.19], [-7, -44.72], [18, -44.72]
|
||||||
|
];
|
||||||
|
|
||||||
|
// AMF (Green Area): 180m long starting at KSF edge, 15m wide
|
||||||
|
const amf = [
|
||||||
|
[18, -44.72], [18, -224.72], [3, -224.72], [3, -44.72], [18, -44.72]
|
||||||
|
];
|
||||||
|
mfParts = [amf];
|
||||||
|
|
||||||
|
// BLF/Road (Blue Area): Complex unified shape
|
||||||
|
blfCoords = [
|
||||||
|
[18, -224.72], [18, 40], [49.5, 40], [49.5, -100],
|
||||||
|
[24, -100], [24, -224.72], [18, -224.72]
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// Enercon / Vestas / GE (Standard)
|
||||||
|
blfCoords = [[-41, 9], [-61, 9], [-61, -81], [-41, -81], [-41, 9]];
|
||||||
|
ksfCoords = [[-8, 0], [-36, 0], [-36, -50], [-8, -50], [-8, 0]];
|
||||||
|
mfParts = [
|
||||||
|
[[-36, 0], [-36, 18], [-8, 18], [-8, 0], [-36, 0]],
|
||||||
|
[[12, -62], [-22, -62], [-22, -72], [12, -72], [12, -62]],
|
||||||
|
[[12, 0], [-8, 0], [-8, -50], [-36, -50], [-36, -72], [-22, -72], [-22, -62], [12, -62], [12, 0]],
|
||||||
|
[[-41, 18], [-47, 18], [-47, 9], [-41, 9], [-41, 18]],
|
||||||
|
[[-41, -81], [-47, -81], [-47, -96], [-41, -96], [-41, -81]],
|
||||||
|
[[-36, 18], [-41, 18], [-41, -72], [-36, -72], [-36, 18]]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blf = transform(blfCoords);
|
||||||
|
const ksf = transform(ksfCoords);
|
||||||
|
|
||||||
|
// Create the feature collection of polygons
|
||||||
|
const mf = {
|
||||||
|
type: "FeatureCollection",
|
||||||
|
features: mfParts.map(part => transform(part))
|
||||||
|
};
|
||||||
|
|
||||||
|
return { sweptArea, techDist, techDistSmall, loadRadius, foundation, blf, ksf, mf, totalHeight, utmCoords };
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Fehler in calculateGeometries:", err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateEditPanelPosition() {
|
||||||
|
if (!activeTurbine || editPanel.style.display === 'none') return;
|
||||||
|
|
||||||
|
const centerPx = state.map.latLngToContainerPoint(activeTurbine.latlng);
|
||||||
|
const sidebarWidth = 260;
|
||||||
|
const panelWidth = 210;
|
||||||
|
|
||||||
|
// Position it clearly to the LEFT of the turbine center
|
||||||
|
// Right edge of panel should be 120px to the left of the center
|
||||||
|
let left = (centerPx.x + sidebarWidth) - panelWidth - 120;
|
||||||
|
let top = centerPx.y - 120;
|
||||||
|
|
||||||
|
// Screen boundaries
|
||||||
|
if (left < sidebarWidth + 10) left = sidebarWidth + 10;
|
||||||
|
if (top < 10) top = 10;
|
||||||
|
if (left + panelWidth > window.innerWidth - 10) left = window.innerWidth - panelWidth - 10;
|
||||||
|
|
||||||
|
editPanel.style.left = `${left}px`;
|
||||||
|
editPanel.style.top = `${top}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditPanel(turbine) {
|
||||||
|
if (activeTurbine && activeTurbine !== turbine) {
|
||||||
|
variantLayers[activeTurbine.variant].removeLayer(activeTurbine.layers.rotationHandle);
|
||||||
|
}
|
||||||
|
activeTurbine = turbine;
|
||||||
|
editNr.value = turbine.nr;
|
||||||
|
let matched = false;
|
||||||
|
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;
|
||||||
|
editNh.value = turbine.hh;
|
||||||
|
editFr.value = turbine.fr || 15;
|
||||||
|
editKsfAngle.value = turbine.ksfAngle || 0;
|
||||||
|
editKsfMirrored.checked = !!turbine.ksfMirrored;
|
||||||
|
|
||||||
|
// Show rotation handle
|
||||||
|
variantLayers[turbine.variant].addLayer(turbine.layers.rotationHandle);
|
||||||
|
|
||||||
|
editPanel.style.display = 'block';
|
||||||
|
updateEditPanelPosition();
|
||||||
|
|
||||||
|
document.getElementById('statusInfo').innerText = `Bearbeite WEA ${turbine.nr}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeEditPanel() {
|
||||||
|
if (activeTurbine) {
|
||||||
|
variantLayers[activeTurbine.variant].removeLayer(activeTurbine.layers.rotationHandle);
|
||||||
|
}
|
||||||
|
activeTurbine = null;
|
||||||
|
editPanel.style.display = 'none';
|
||||||
|
document.getElementById('statusInfo').innerText = "Bereit.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reposition floating panel on map movement
|
||||||
|
state.map.on('move zoom', updateEditPanelPosition);
|
||||||
|
|
||||||
|
// Legend Logic
|
||||||
|
function updateLegend() {
|
||||||
|
if (!legendContent) return;
|
||||||
|
|
||||||
|
let html = '<div class="legend-section-title">Anlagen-Geometrien</div>';
|
||||||
|
if (state.turbines.length > 0) {
|
||||||
|
html += `
|
||||||
|
<div class="legend-item"><span class="color-box" style="background: #00c8ff;"></span> Rotorfläche</div>
|
||||||
|
<div class="legend-item"><span class="color-box" style="border: 2px dashed #ffcc00; background: transparent;"></span> Techn. Abstand (Ellipse)</div>
|
||||||
|
<div class="legend-item"><span class="color-box" style="border: 1.5px dotted #ffcc00; background: transparent;"></span> Techn. Abstand (Circle)</div>
|
||||||
|
<div class="legend-item"><span class="color-box" style="background: rgba(255, 68, 68, 0.2); border: 1px solid #ff4444;"></span> Auflastenradius</div>
|
||||||
|
<div class="legend-item"><span class="color-box" style="background: rgba(52, 152, 219, 0.3); border: 1px solid #3498db;"></span> Fundament</div>
|
||||||
|
<div class="legend-item"><span class="color-box" style="background: #e74c3c; opacity: 0.6;"></span> Kranstellfläche (KSF)</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
html += '<div style="font-size: 0.7rem; opacity: 0.6; padding-left: 20px;">Keine Anlagen gesetzt</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '<div class="legend-section-title" style="margin-top: 15px;">Sicherungsstand (ALKIS)</div>';
|
||||||
|
Object.keys(STATUS_MAP).forEach(status => {
|
||||||
|
const data = STATUS_MAP[status];
|
||||||
|
html += `
|
||||||
|
<div class="legend-item-status">
|
||||||
|
<span class="status-dot" style="background: ${data.color};"></span>
|
||||||
|
<div class="status-text-container">
|
||||||
|
<div class="status-label">${status}</div>
|
||||||
|
<div class="status-desc">${data.desc}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
legendContent.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Legend collapse
|
||||||
|
legendHeader.addEventListener('click', () => {
|
||||||
|
floatingLegend.classList.toggle('collapsed');
|
||||||
|
btnToggleLegend.innerHTML = floatingLegend.classList.contains('collapsed') ? '▼' : '▲';
|
||||||
|
});
|
||||||
|
|
||||||
state.map.on('overlayadd overlayremove', updateLegend);
|
state.map.on('overlayadd overlayremove', updateLegend);
|
||||||
// Also update on variant change
|
// Also update on variant change
|
||||||
variantTabs.forEach(tab => {
|
variantTabs.forEach(tab => {
|
||||||
|
|
@ -386,11 +581,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const fr = overrideData?.fr || Number.parseFloat(inputFoundation?.value) || 15;
|
const fr = overrideData?.fr || Number.parseFloat(inputFoundation?.value) || 15;
|
||||||
let type = overrideData?.type || "Standard Typ";
|
let type = overrideData?.type || "Standard Typ";
|
||||||
let hersteller = overrideData?.hersteller || "Unbekannt";
|
let hersteller = overrideData?.hersteller || "Unbekannt";
|
||||||
|
let mw = overrideData?.mw || null;
|
||||||
if (!overrideData && weaTypeSelector && weaTypeSelector.value) {
|
if (!overrideData && weaTypeSelector && weaTypeSelector.value) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(weaTypeSelector.value);
|
const parsed = JSON.parse(weaTypeSelector.value.replace(/"/g, '"'));
|
||||||
type = parsed.anlagentyp;
|
type = parsed.anlagentyp;
|
||||||
hersteller = parsed.hersteller;
|
hersteller = parsed.hersteller;
|
||||||
|
mw = parsed.nennleistung;
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
const weaNr = overrideData?.nr || loadedNr || (state.turbines.length + 1).toString();
|
const weaNr = overrideData?.nr || loadedNr || (state.turbines.length + 1).toString();
|
||||||
|
|
@ -418,7 +615,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
id: `WEA_${Date.now()}`,
|
id: `WEA_${Date.now()}`,
|
||||||
nr: weaNr,
|
nr: weaNr,
|
||||||
variant: overrideData?.variant || state.activeVariant,
|
variant: overrideData?.variant || state.activeVariant,
|
||||||
hersteller, type, rd, hh, fr, latlng,
|
hersteller, type, mw, rd, hh, fr, latlng,
|
||||||
ksfAngle, ksfMirrored,
|
ksfAngle, ksfMirrored,
|
||||||
totalHeight: geoms.totalHeight,
|
totalHeight: geoms.totalHeight,
|
||||||
layers: {
|
layers: {
|
||||||
|
|
@ -555,11 +752,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const newNr = editNr.value;
|
const newNr = editNr.value;
|
||||||
let newManufacturer = activeTurbine.hersteller;
|
let newManufacturer = activeTurbine.hersteller;
|
||||||
let newType = activeTurbine.type;
|
let newType = activeTurbine.type;
|
||||||
|
let newMw = activeTurbine.mw;
|
||||||
if (editWeaSelector && editWeaSelector.value) {
|
if (editWeaSelector && editWeaSelector.value) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(editWeaSelector.value);
|
const parsed = JSON.parse(editWeaSelector.value.replace(/"/g, '"'));
|
||||||
newManufacturer = parsed.hersteller;
|
newManufacturer = parsed.hersteller;
|
||||||
newType = parsed.anlagentyp;
|
newType = parsed.anlagentyp;
|
||||||
|
newMw = parsed.nennleistung;
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
const newRd = Number.parseFloat(editRd.value);
|
const newRd = Number.parseFloat(editRd.value);
|
||||||
|
|
@ -575,6 +774,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
activeTurbine.nr = newNr;
|
activeTurbine.nr = newNr;
|
||||||
activeTurbine.hersteller = newManufacturer;
|
activeTurbine.hersteller = newManufacturer;
|
||||||
activeTurbine.type = newType;
|
activeTurbine.type = newType;
|
||||||
|
activeTurbine.mw = newMw;
|
||||||
activeTurbine.rd = newRd;
|
activeTurbine.rd = newRd;
|
||||||
activeTurbine.hh = newNh;
|
activeTurbine.hh = newNh;
|
||||||
activeTurbine.fr = newFr;
|
activeTurbine.fr = newFr;
|
||||||
|
|
@ -2335,424 +2535,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
// Start loading process
|
// Start loading process
|
||||||
await loadWeaTypes();
|
await loadWeaTypes();
|
||||||
initializeProjects();
|
|
||||||
|
|
||||||
// Project Persistence
|
|
||||||
const btnSave = document.getElementById('btnSaveProject');
|
|
||||||
const btnLoad = document.getElementById('btnLoadProject');
|
|
||||||
const projectInput = document.getElementById('projectInput');
|
|
||||||
|
|
||||||
btnSave.addEventListener('click', () => {
|
|
||||||
const projectData = {
|
|
||||||
version: "1.0",
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
config: state.config,
|
|
||||||
turbines: state.turbines.map(t => ({
|
|
||||||
id: t.id,
|
|
||||||
nr: t.nr,
|
|
||||||
variant: t.variant,
|
|
||||||
type: t.type,
|
|
||||||
rd: t.rd,
|
|
||||||
hh: t.hh,
|
|
||||||
latlng: t.layers.marker.getLatLng()
|
|
||||||
})),
|
|
||||||
ownerMapping: state.ownerMapping,
|
|
||||||
ownerStatuses: state.ownerStatuses
|
|
||||||
};
|
|
||||||
|
|
||||||
const blob = new Blob([JSON.stringify(projectData, null, 2)], { type: 'application/json' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = `WindPlan_Projekt_${new Date().toLocaleDateString()}.json`;
|
|
||||||
a.click();
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
document.getElementById('statusInfo').innerText = "Projekt lokal exportiert.";
|
|
||||||
});
|
|
||||||
|
|
||||||
// DB Persistence
|
|
||||||
let autoSaveTimeout = null;
|
|
||||||
function triggerAutoSave() {
|
|
||||||
if (state.isLoading) {
|
|
||||||
console.log("Auto-Save unterdrückt: App befindet sich noch in der Ladephase.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!state.hasLoadedFromDB) {
|
|
||||||
console.warn("Auto-Save unterdrückt: Datenbank wurde beim Start nicht erfolgreich geladen.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
|
|
||||||
autoSaveTimeout = setTimeout(() => {
|
|
||||||
saveTurbinesToDB(false);
|
|
||||||
}, 1500); // 1.5 Seconds debounce
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveTurbinesToDB(force = false) {
|
|
||||||
if (state.isLoading && !force) {
|
|
||||||
console.log("Speichern unterdrückt: Ladephase aktiv.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!state.hasLoadedFromDB && !force) {
|
|
||||||
console.warn("Speichern unterdrückt: Datenbank wurde beim Start nicht geladen.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.hasLoadedFromDB && force) {
|
|
||||||
const confirmSave = confirm("WARNUNG: Die Datenbank wurde beim Start nicht erfolgreich geladen. Wenn Sie jetzt speichern, überschreiben Sie eventuell vorhandene Standorte in der Datenbank. Möchten Sie trotzdem speichern?");
|
|
||||||
if (!confirmSave) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusEl = document.getElementById('statusInfo');
|
|
||||||
if (statusEl) statusEl.innerText = "Speicherung läuft...";
|
|
||||||
|
|
||||||
const projekt_id = getProjektId();
|
|
||||||
const turbineData = state.turbines.map(t => ({
|
|
||||||
nr: t.nr,
|
|
||||||
variant: t.variant,
|
|
||||||
hersteller: t.hersteller,
|
|
||||||
type: t.type,
|
|
||||||
rd: t.rd,
|
|
||||||
hh: t.hh,
|
|
||||||
ksfAngle: t.ksfAngle,
|
|
||||||
latlng: t.layers.marker.getLatLng()
|
|
||||||
}));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/wea', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ projekt_id, turbines: turbineData })
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
if (response.ok) {
|
|
||||||
if (statusEl) statusEl.innerHTML = `<span style="color: #2ecc71;">${result.message}</span>`;
|
|
||||||
} else {
|
|
||||||
throw new Error(result.error || "Fehler beim Speichern in DB");
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
if (statusEl) statusEl.innerHTML = `<span style="color: #ff4444;">Speicherfehler: ${err.message}</span>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const btnSaveDB = document.getElementById('btnSaveDB');
|
|
||||||
if (btnSaveDB) {
|
|
||||||
btnSaveDB.addEventListener('click', () => saveTurbinesToDB(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadTurbinesFromDB() {
|
|
||||||
const projekt_id = getProjektId();
|
|
||||||
const statusEl = document.getElementById('statusInfo');
|
|
||||||
console.log(`Lade WEAs aus Datenbank für Projekt: ${projekt_id}...`);
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/wea/${projekt_id}`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP-Fehler! Status: ${response.status}`);
|
|
||||||
}
|
|
||||||
const dbTurbines = await response.json();
|
|
||||||
console.log(`Datenbank-Response: ${dbTurbines.length} WEAs erhalten.`);
|
|
||||||
|
|
||||||
// Clear existing turbines safely
|
|
||||||
state.turbines.forEach(t => {
|
|
||||||
try {
|
|
||||||
if (t.layers && t.variant && variantLayers[t.variant]) {
|
|
||||||
Object.values(t.layers).forEach(l => variantLayers[t.variant].removeLayer(l));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Error clearing existing layers:", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
state.turbines = [];
|
|
||||||
|
|
||||||
if (dbTurbines && dbTurbines.length > 0) {
|
|
||||||
dbTurbines.forEach(t => {
|
|
||||||
try {
|
|
||||||
const latlng = L.latLng(t.lat, t.lng);
|
|
||||||
const variant = t.variant || 'A';
|
|
||||||
|
|
||||||
if (!variantLayers[variant]) {
|
|
||||||
console.warn(`Ungültige Variante: ${variant}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
createTurbine(latlng, null, {
|
|
||||||
nr: t.nr,
|
|
||||||
hersteller: t.hersteller,
|
|
||||||
type: t.type,
|
|
||||||
rd: Number.parseFloat(t.rd) || 160,
|
|
||||||
hh: Number.parseFloat(t.hh) || 165,
|
|
||||||
ksfAngle: Number.parseFloat(t.ksfangle ?? t.ksfAngle ?? 0),
|
|
||||||
variant: variant
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Fehler bei WEA ${t.nr}:`, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
statusEl.innerText = `${dbTurbines.length} WEAs aus Datenbank geladen.`;
|
|
||||||
} else {
|
|
||||||
console.log("Datenbank ist leer.");
|
|
||||||
}
|
|
||||||
state.hasLoadedFromDB = true;
|
|
||||||
} catch (err) {
|
|
||||||
console.error("GLOBALER FEHLER beim Laden aus der DB:", err);
|
|
||||||
if (statusEl) statusEl.innerText = "Fehler beim Laden der Standorte.";
|
|
||||||
state.hasLoadedFromDB = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
btnLoad.addEventListener('click', () => projectInput.click());
|
|
||||||
|
|
||||||
projectInput.addEventListener('change', async (e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const text = await file.text();
|
|
||||||
const data = JSON.parse(text);
|
|
||||||
|
|
||||||
// Clear existing turbines
|
|
||||||
state.turbines.forEach(t => {
|
|
||||||
Object.values(t.layers).forEach(l => {
|
|
||||||
variantLayers[t.variant].removeLayer(l);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
state.turbines = [];
|
|
||||||
|
|
||||||
// Restore Owner Data
|
|
||||||
state.ownerMapping = data.ownerMapping || null;
|
|
||||||
state.ownerStatuses = data.ownerStatuses || {};
|
|
||||||
|
|
||||||
// Reconstruct from data
|
|
||||||
data.turbines.forEach(tData => {
|
|
||||||
// Set current UI state momentarily for creating turbine correctly
|
|
||||||
// (Easier than refactoring createTurbine now)
|
|
||||||
if (inputType) inputType.value = tData.type;
|
|
||||||
if (inputRotor) inputRotor.value = tData.rd;
|
|
||||||
if (inputHub) inputHub.value = tData.hh;
|
|
||||||
|
|
||||||
const oldVariant = state.activeVariant;
|
|
||||||
state.activeVariant = tData.variant;
|
|
||||||
|
|
||||||
createTurbine(tData.latlng, tData.nr);
|
|
||||||
|
|
||||||
state.activeVariant = oldVariant;
|
|
||||||
});
|
|
||||||
|
|
||||||
updateProximityLines();
|
|
||||||
updateLegend();
|
|
||||||
triggerAutoSave();
|
|
||||||
|
|
||||||
document.getElementById('statusInfo').innerText = "Projekt geladen.";
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Fehler beim Laden:", err);
|
|
||||||
alert("Ungültige Projektdatei.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Geolocation Logic
|
|
||||||
let watchId = null;
|
|
||||||
let userMarker = null;
|
|
||||||
let userAccuracyCircle = null;
|
|
||||||
const btnLocate = document.getElementById('btnLocate');
|
|
||||||
|
|
||||||
function stopTracking() {
|
|
||||||
if (watchId !== null) {
|
|
||||||
navigator.geolocation.clearWatch(watchId);
|
|
||||||
watchId = null;
|
|
||||||
}
|
|
||||||
if (userMarker) state.map.removeLayer(userMarker);
|
|
||||||
if (userAccuracyCircle) state.map.removeLayer(userAccuracyCircle);
|
|
||||||
userMarker = null;
|
|
||||||
userAccuracyCircle = null;
|
|
||||||
btnLocate.classList.remove('active');
|
|
||||||
document.getElementById('statusInfo').innerText = "Standortverfolgung beendet.";
|
|
||||||
}
|
|
||||||
|
|
||||||
function startTracking() {
|
|
||||||
if (!("geolocation" in navigator)) {
|
|
||||||
alert("Geolocation wird von Ihrem Browser nicht unterstützt.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
btnLocate.classList.add('active');
|
|
||||||
document.getElementById('statusInfo').innerText = "Suche Standort...";
|
|
||||||
|
|
||||||
watchId = navigator.geolocation.watchPosition((position) => {
|
|
||||||
const { latitude, longitude, accuracy } = position.coords;
|
|
||||||
const latlng = L.latLng(latitude, longitude);
|
|
||||||
|
|
||||||
if (userMarker) {
|
|
||||||
userMarker.setLatLng(latlng);
|
|
||||||
userAccuracyCircle.setLatLng(latlng);
|
|
||||||
userAccuracyCircle.setRadius(accuracy);
|
|
||||||
} else {
|
|
||||||
userMarker = L.marker(latlng, {
|
|
||||||
icon: L.divIcon({
|
|
||||||
className: 'user-location-marker pulse',
|
|
||||||
iconSize: [16, 16],
|
|
||||||
iconAnchor: [8, 8]
|
|
||||||
})
|
|
||||||
}).addTo(state.map);
|
|
||||||
|
|
||||||
userAccuracyCircle = L.circle(latlng, {
|
|
||||||
radius: accuracy,
|
|
||||||
className: 'user-location-accuracy'
|
|
||||||
}).addTo(state.map);
|
|
||||||
|
|
||||||
// Center on first found position
|
|
||||||
state.map.setView(latlng, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('statusInfo').innerText = `Standort aktualisiert (Genauigkeit: ${accuracy.toFixed(0)}m)`;
|
|
||||||
}, (err) => {
|
|
||||||
console.error("Geolocation error code:", err.code, err.message);
|
|
||||||
let msg = "Fehler bei der Standorterkennung.";
|
|
||||||
if (err.code === 1) msg = "Standortzugriff verweigert. Bitte erlauben Sie den Zugriff in den Browser-Einstellungen.";
|
|
||||||
else if (err.code === 2) msg = "Standort nicht verfügbar. Prüfen Sie, ob GPS/Ortungsdienste am Gerät aktiviert sind.";
|
|
||||||
else if (err.code === 3) msg = "Zeitüberschreitung bei der Standorterkennung.";
|
|
||||||
|
|
||||||
alert(msg);
|
|
||||||
stopTracking();
|
|
||||||
}, {
|
|
||||||
enableHighAccuracy: true,
|
|
||||||
timeout: 30000,
|
|
||||||
maximumAge: 30000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (btnLocate) {
|
|
||||||
btnLocate.onclick = () => {
|
|
||||||
if (watchId === null) {
|
|
||||||
startTracking();
|
|
||||||
} else {
|
|
||||||
stopTracking();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize Edit Mode Toggle
|
|
||||||
const btnToggleEditMode = document.getElementById('btnToggleEditMode');
|
|
||||||
const editModeIcon = document.getElementById('editModeIcon');
|
|
||||||
const editModeText = document.getElementById('editModeText');
|
|
||||||
|
|
||||||
function updateEditModeUI() {
|
|
||||||
if (state.isEditMode) {
|
|
||||||
editModeIcon.innerText = '🔓';
|
|
||||||
editModeText.innerText = 'Bearbeitung aktiv';
|
|
||||||
btnToggleEditMode.classList.replace('btn-secondary', 'btn-primary');
|
|
||||||
btnPlaceTurbine.disabled = false;
|
|
||||||
btnPlaceTurbine.style.opacity = '1';
|
|
||||||
btnCreateAtUTM.disabled = false;
|
|
||||||
btnCreateAtUTM.style.opacity = '1';
|
|
||||||
if (btnManageOwners) {
|
|
||||||
btnManageOwners.disabled = false;
|
|
||||||
btnManageOwners.style.opacity = '1';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
editModeIcon.innerText = '🔒';
|
|
||||||
editModeText.innerText = 'Bearbeitung gesperrt';
|
|
||||||
btnToggleEditMode.classList.replace('btn-primary', 'btn-secondary');
|
|
||||||
btnPlaceTurbine.disabled = true;
|
|
||||||
btnPlaceTurbine.style.opacity = '0.5';
|
|
||||||
btnCreateAtUTM.disabled = true;
|
|
||||||
btnCreateAtUTM.style.opacity = '0.5';
|
|
||||||
if (btnManageOwners) {
|
|
||||||
btnManageOwners.disabled = true;
|
|
||||||
btnManageOwners.style.opacity = '0.5';
|
|
||||||
}
|
|
||||||
|
|
||||||
closeEditPanel();
|
|
||||||
if (ownerModal) ownerModal.style.display = 'none';
|
|
||||||
if (placementMode) {
|
|
||||||
placementMode = false;
|
|
||||||
btnPlaceTurbine.classList.remove('active');
|
|
||||||
state.map.getContainer().style.cursor = '';
|
|
||||||
state.map.getContainer().classList.remove('placement-active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.turbines.forEach(t => {
|
|
||||||
if (state.isEditMode) {
|
|
||||||
t.layers.marker.dragging.enable();
|
|
||||||
t.layers.rotationHandle.dragging.enable();
|
|
||||||
} else {
|
|
||||||
t.layers.marker.dragging.disable();
|
|
||||||
t.layers.rotationHandle.dragging.disable();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (btnToggleEditMode) {
|
|
||||||
btnToggleEditMode.addEventListener('click', () => {
|
|
||||||
state.isEditMode = !state.isEditMode;
|
|
||||||
updateEditModeUI();
|
|
||||||
});
|
|
||||||
// Set initial state (locked)
|
|
||||||
updateEditModeUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateLegend();
|
|
||||||
console.log("WindPlaner initialisiert.");
|
|
||||||
const btnExportCoordsPDF = document.getElementById('btnExportCoordsPDF');
|
|
||||||
if (btnExportCoordsPDF) {
|
|
||||||
btnExportCoordsPDF.addEventListener('click', async () => {
|
|
||||||
const originalText = btnExportCoordsPDF.innerText;
|
|
||||||
btnExportCoordsPDF.innerText = 'Exportiere...';
|
|
||||||
btnExportCoordsPDF.disabled = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Finde aktive Variante
|
|
||||||
const activeVariantNode = document.querySelector('.variant-tab.active');
|
|
||||||
const activeVariant = activeVariantNode ? activeVariantNode.dataset.variant : 'variant1';
|
|
||||||
|
|
||||||
// Filtere Anlagen, die zur aktuellen Variante gehören
|
|
||||||
const activeTurbines = state.turbines.filter(t => t.variant === activeVariant);
|
|
||||||
|
|
||||||
if (activeTurbines.length === 0) {
|
|
||||||
alert('Keine Anlagen in der aktuellen Variante gefunden.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by WEA Nr (numerically if possible)
|
|
||||||
activeTurbines.sort((a, b) => {
|
|
||||||
const numA = parseInt(a.nr) || 0;
|
|
||||||
const numB = parseInt(b.nr) || 0;
|
|
||||||
return numA - numB;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialisiere jsPDF
|
|
||||||
const { jsPDF } = window.jspdf;
|
|
||||||
const doc = new jsPDF();
|
|
||||||
|
|
||||||
// Titel und Projektname
|
|
||||||
const projNameForFile = document.getElementById('projectSelector')?.options[document.getElementById('projectSelector')?.selectedIndex]?.text || 'Projekt unbekannt';
|
|
||||||
|
|
||||||
doc.setFontSize(16);
|
|
||||||
doc.text(`Koordinatenliste`, 14, 20);
|
|
||||||
|
|
||||||
// Untertitel (Variante)
|
|
||||||
doc.setFontSize(12);
|
|
||||||
let variantName = activeVariant === 'variant1' ? 'Variante 1' : activeVariant === 'variant2' ? 'Variante 2' : 'Variante 3';
|
|
||||||
doc.text(`Variante: ${variantName}`, 14, 30);
|
|
||||||
|
|
||||||
// Daten vorbereiten
|
|
||||||
const tableData = activeTurbines.map(t => {
|
|
||||||
const latlng = t.layers.marker.getLatLng();
|
|
||||||
const utmCoords = proj4(wgs84, utm32, [latlng.lng, latlng.lat]);
|
|
||||||
|
|
||||||
// Nennleistung aus loaded types finden
|
|
||||||
let nennleistung = '-';
|
|
||||||
if (window.availableWeaTypes) {
|
|
||||||
const matchedType = window.availableWeaTypes.find(w => w.hersteller === t.hersteller && w.anlagentyp === t.type);
|
|
||||||
if (matchedType && matchedType.nennleistung) {
|
|
||||||
nennleistung = matchedType.nennleistung;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const anlagentyp = t.type === 'Standard Typ' ? '-' : (t.type || '-');
|
|
||||||
|
|
||||||
return [
|
|
||||||
t.nr,
|
t.nr,
|
||||||
t.hersteller || '-',
|
t.hersteller || '-',
|
||||||
anlagentyp,
|
anlagentyp,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue