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') ? '▼' : '▲';
|
||||
});
|
||||
|
||||
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);
|
||||
// Also update on variant change
|
||||
variantTabs.forEach(tab => {
|
||||
|
|
@ -386,11 +581,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
const fr = overrideData?.fr || Number.parseFloat(inputFoundation?.value) || 15;
|
||||
let type = overrideData?.type || "Standard Typ";
|
||||
let hersteller = overrideData?.hersteller || "Unbekannt";
|
||||
let mw = overrideData?.mw || null;
|
||||
if (!overrideData && weaTypeSelector && weaTypeSelector.value) {
|
||||
try {
|
||||
const parsed = JSON.parse(weaTypeSelector.value);
|
||||
const parsed = JSON.parse(weaTypeSelector.value.replace(/"/g, '"'));
|
||||
type = parsed.anlagentyp;
|
||||
hersteller = parsed.hersteller;
|
||||
mw = parsed.nennleistung;
|
||||
} catch(e) {}
|
||||
}
|
||||
const weaNr = overrideData?.nr || loadedNr || (state.turbines.length + 1).toString();
|
||||
|
|
@ -418,7 +615,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
id: `WEA_${Date.now()}`,
|
||||
nr: weaNr,
|
||||
variant: overrideData?.variant || state.activeVariant,
|
||||
hersteller, type, rd, hh, fr, latlng,
|
||||
hersteller, type, mw, rd, hh, fr, latlng,
|
||||
ksfAngle, ksfMirrored,
|
||||
totalHeight: geoms.totalHeight,
|
||||
layers: {
|
||||
|
|
@ -555,11 +752,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
const newNr = editNr.value;
|
||||
let newManufacturer = activeTurbine.hersteller;
|
||||
let newType = activeTurbine.type;
|
||||
let newMw = activeTurbine.mw;
|
||||
if (editWeaSelector && editWeaSelector.value) {
|
||||
try {
|
||||
const parsed = JSON.parse(editWeaSelector.value);
|
||||
const parsed = JSON.parse(editWeaSelector.value.replace(/"/g, '"'));
|
||||
newManufacturer = parsed.hersteller;
|
||||
newType = parsed.anlagentyp;
|
||||
newMw = parsed.nennleistung;
|
||||
} catch(e) {}
|
||||
}
|
||||
const newRd = Number.parseFloat(editRd.value);
|
||||
|
|
@ -575,6 +774,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
activeTurbine.nr = newNr;
|
||||
activeTurbine.hersteller = newManufacturer;
|
||||
activeTurbine.type = newType;
|
||||
activeTurbine.mw = newMw;
|
||||
activeTurbine.rd = newRd;
|
||||
activeTurbine.hh = newNh;
|
||||
activeTurbine.fr = newFr;
|
||||
|
|
@ -2335,424 +2535,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
|
||||
// Start loading process
|
||||
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.hersteller || '-',
|
||||
anlagentyp,
|
||||
|
|
|
|||
Loading…
Reference in New Issue