Files
EA-Bilanz/js/app.js
T
erikandClaude Fable 5.1 2b59cedb25 Erfassung Eingriffs- und Kompensationsbilanz Sachsen (Prototyp)
Web-Erfassung für das Biotopwertverfahren nach der Handlungsempfehlung
Sachsen 2009 in der Fassung der Anlagen zur Sächsischen
Kompensationsverordnung (Stand 01/2017): HTML, Bootstrap 5, jQuery,
bootstrap-select, OpenLayers 10 (EPSG:25833, GeoSN-WMS).

- Rechenkern für Formblätter F1–F14 (js/bilanz.js)
- Karte mit Zeichenwerkzeugen, Wirkzonen, GeoJSON-Export (js/karte.js)
- Biotopwertliste Anlage 2 als JSON, per pdfplumber aus der PDF
  extrahiert (tools/parse_biotopwertliste.py), Faktoren der Anlagen
  1, 3, 4, 6.x (data/faktoren.json)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 09:32:02 +02:00

852 lines
85 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* app.js – Oberfläche: Zustand, Karten-Anbindung, Formulare, Formblätter */
(function () {
'use strict';
const $ = window.jQuery;
const LS_KEY = 'eingriffsbilanz.projekt.v1';
let P = null, FK = null, BWL = null;
let OPT_BW = '', OPT_PW = '', FUNK_ALL = [];
let drawCtx = null, aasCtx = null, saveTimer = null, selectedFid = null;
/* ------------------------------------------------------------ Helfer */
const num = BIL.num;
const fmtWP = n => (Math.round((num(n) + Number.EPSILON) * 100) / 100).toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const fmtM2 = n => Math.round(num(n)).toLocaleString('de-DE');
const fmtF = n => (n === null || n === undefined || n === '') ? '–' : (+n).toLocaleString('de-DE', { minimumFractionDigits: 1, maximumFractionDigits: 2 });
const fmtEur = n => Math.round(num(n)).toLocaleString('de-DE') + ' €';
const esc = s => $('<div>').text(s === null || s === undefined ? '' : s).html();
const today = () => new Date().toISOString().slice(0, 10);
const uid = () => P.seq++;
const byBtc = btc => BIL.byBtc[btc];
const btLabel = btc => { const t = byBtc(btc); return t ? t.btc + ' ' + t.name : (btc || '–'); };
const signed = d => (d > 0 ? '+' : '') + d;
function toast(msg, cls) {
const $t = $('<div class="toast align-items-center text-bg-' + (cls || 'dark') + ' border-0" role="alert"><div class="d-flex"><div class="toast-body small">' + esc(msg) + '</div><button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button></div></div>');
$('#toasts').append($t);
bootstrap.Toast.getOrCreateInstance($t[0], { delay: 3500 }).show();
$t.on('hidden.bs.toast', () => $t.remove());
}
function showTab(id) { bootstrap.Tab.getOrCreateInstance(document.querySelector('[data-bs-target="#' + id + '"]')).show(); }
function neuesProjekt() {
return {
version: 1, seq: 1,
meta: { name: '', vkz: '', ort: '', traeger: '', bearbeiter: '', datum: today(), unb: '', beschreibung: '', einfachfall: false, fall_b: false, vermeidung: '', konzept: '' },
eingriff: {
flaechen: [], funktionen: [],
landschaft: { typ: 'keiner', hoehe: '', radius1: '', radius2_override: '', standort_fid: null, anteile: { 1: {}, 2: {} }, fwz1: '', fwz2: '', fwz1_manuell: false, fwz2_manuell: false, aufwertungen: [], baukosten: '', abl: {}, repowering: 'keins' }
},
massnahmen: [],
features: { type: 'FeatureCollection', features: [] }
};
}
/* ----------------------------------------------------- Persistenz */
function scheduleSave() { clearTimeout(saveTimer); saveTimer = setTimeout(save, 400); }
function snapshot() { P.features = KARTE.toGeoJSON(); return P; }
function save() {
try {
localStorage.setItem(LS_KEY, JSON.stringify(snapshot()));
$('#autosave-info').text('zwischengespeichert ' + new Date().toLocaleTimeString('de-DE'));
} catch (e) { console.warn(e); }
}
function loadProjekt(obj) {
const fresh = neuesProjekt();
P = Object.assign(fresh, obj || {});
P.meta = Object.assign(fresh.meta, P.meta || {});
P.eingriff = Object.assign(fresh.eingriff, P.eingriff || {});
P.eingriff.landschaft = Object.assign(fresh.eingriff.landschaft, P.eingriff.landschaft || {});
P.eingriff.landschaft.anteile = Object.assign({ 1: {}, 2: {} }, P.eingriff.landschaft.anteile || {});
P.massnahmen = P.massnahmen || [];
KARTE.loadGeoJSON(P.features);
renderAll();
KARTE.fitAll();
updateZonen();
}
function download(name, text, mime) {
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([text], { type: mime || 'application/json' }));
a.download = name; document.body.appendChild(a); a.click();
setTimeout(() => { URL.revokeObjectURL(a.href); a.remove(); }, 500);
}
const dateiname = suffix => ((P.meta.name || 'vorhaben').replace(/[^\wäöüÄÖÜß-]+/g, '_') + suffix);
/* --------------------------------------------- Optionen Biotoptypen */
function buildOptions() {
const grp = {};
BWL.hauptgruppen.forEach(h => { grp[h.nr] = { name: h.name, bw: [], pw: [] }; });
BWL.typen.forEach(t => {
if (t.ebene !== 'blatt') return;
const sub = 'BW ' + (t.bw ?? '–') + ' · PW ' + (t.pw ?? '–') + ' · KAG ' + (t.kag || '–') + (t.schutz !== '–' ? ' · ' + t.schutz : '') + (t.lrt !== '–' ? ' · LRT ' + t.lrt : '');
const opt = '<option value="' + t.btc + '" data-subtext="' + esc(sub) + '" data-tokens="' + esc(t.name + ' ' + t.btc.replace(/\./g, '')) + '">' + t.btc + ' ' + esc(t.name) + '</option>';
if (t.bw !== null) grp[t.hg].bw.push(opt);
if (t.pw !== null) grp[t.hg].pw.push(opt);
});
OPT_BW = ''; OPT_PW = '';
Object.keys(grp).forEach(k => {
const label = k + ' ' + esc(grp[k].name);
if (grp[k].bw.length) OPT_BW += '<optgroup label="' + label + '">' + grp[k].bw.join('') + '</optgroup>';
if (grp[k].pw.length) OPT_PW += '<optgroup label="' + label + '">' + grp[k].pw.join('') + '</optgroup>';
});
}
function btSelect(field, value, art, title) {
const t = title || 'Biotoptyp wählen …';
return '<select class="bt-select" data-f="' + field + '" data-art="' + art + '" title="' + t + '"><option value="">' + t + '</option>' + (art === 'bw' ? OPT_BW : OPT_PW) + '</select>';
}
function initSelects($root) {
$root.find('select.bt-select').each(function () {
const $s = $(this);
if ($s.data('selectpicker')) return;
const val = $s.attr('data-value');
$s.val(val || '');
$s.selectpicker({ liveSearch: true, size: 10, width: '100%', liveSearchPlaceholder: 'Suchen: Code oder Bezeichnung', noneSelectedText: $s.attr('title') || 'Biotoptyp wählen …', showSubtext: true, liveSearchNormalize: true, dropupAuto: false, container: 'body' });
});
}
function optList(list, value, keyF, labelF) {
return list.map(o => { const k = keyF(o); return '<option value="' + esc(k) + '"' + (String(k) === String(value) ? ' selected' : '') + '>' + esc(labelF(o)) + '</option>'; }).join('');
}
const stufenOpts = v => optList(FK.bewertungsstufen, v, s => s, s => s);
const ewbOpts = (v, list) => optList(list || ['UE', 'EE'], v, s => s, s => s + ' – ' + FK.ewb[s]);
const afbOpts = (v, keys) => optList(keys, v, s => s, s => s + ' – ' + FK.afb[s]);
const funkOpts = (v, list) => optList(list, v, f => f.key, f => f.name);
/* ---------------------------------------------------------- AAS */
function aasText(list) {
const s = BIL.aasSumme(list);
if (!list || !list.length) return '±0';
return signed(s) + ' (' + list.map(a => a.key || 'frei').join(', ') + ')';
}
function openAas(ctx) {
aasCtx = ctx;
const rec = ctx.rec, bt = byBtc(rec[ctx.btField]);
if (!bt) { toast('Zuerst Biotoptyp wählen.', 'warning'); return; }
const erlaubt = ctx.bestandSeite ? bt.aas_b : bt.aas_p;
const cur = rec[ctx.aasField] || [];
let h = '<p class="mb-2"><b>' + esc(bt.btc + ' ' + bt.name) + '</b> – ' + (ctx.bestandSeite ? 'Biotopwert ' + bt.bw : 'Planungswert ' + bt.pw) + ' WP/m². Zulässige Auf-/Abschläge lt. Spalte 12: ' + (erlaubt.length ? erlaubt.join(', ') : 'keine') + '.</p>';
FK.aas.filter(a => erlaubt.includes(a.nr)).forEach(a => {
h += '<div class="border rounded p-2 mb-2"><div class="fw-semibold mb-1">AAS ' + a.nr + ' – ' + esc(a.titel) + '</div>';
const gruppen = a.gruppen || [null];
gruppen.forEach(g => {
const name = 'aas-' + a.nr + (g ? '-' + g : '');
if (g) h += '<div class="text-muted mt-1">' + esc(g) + '</div>';
const opts = a.optionen.filter(o => !g || o.gruppe === g);
const selKey = (cur.find(c => opts.some(o => o.key === c.key)) || {}).key || '';
h += '<div class="form-check"><input class="form-check-input" type="radio" name="' + name + '" value="" id="' + name + '-0"' + (selKey ? '' : ' checked') + '><label class="form-check-label" for="' + name + '-0">kein Ansatz</label></div>';
opts.forEach(o => {
h += '<div class="form-check"><input class="form-check-input" type="radio" name="' + name + '" value="' + o.key + '" data-delta="' + o.delta + '" id="' + name + '-' + o.key + '"' + (selKey === o.key ? ' checked' : '') + '><label class="form-check-label" for="' + name + '-' + o.key + '"><span class="badge ' + (o.delta < 0 ? 'text-bg-danger' : 'text-bg-success') + ' me-1">' + signed(o.delta) + '</span>' + esc(o.text) + '</label></div>';
});
});
h += '</div>';
});
const frei = cur.find(c => c.key === 'frei') || {};
h += '<div class="border rounded p-2 mb-2"><div class="fw-semibold mb-1">Abweichung in Absprache mit der Naturschutzbehörde</div><div class="row g-2 align-items-center"><div class="col-auto"><input type="number" class="form-control form-control-sm" id="aas-frei" min="-5" max="5" step="1" style="width:5rem" value="' + (frei.delta ?? '') + '"></div><div class="col"><input type="text" class="form-control form-control-sm" id="aas-frei-txt" placeholder="Begründung" value="' + esc(frei.text || '') + '"></div></div></div>';
$('#aas-body').html(h);
updateAasSumme();
bootstrap.Modal.getOrCreateInstance('#modal-aas').show();
}
function readAas() {
const list = [];
$('#aas-body input[type=radio]:checked').each(function () {
if (!this.value) return;
const nr = parseInt(this.name.split('-')[1], 10);
const a = FK.aas.find(x => x.nr === nr); const o = a.optionen.find(x => x.key === this.value);
list.push({ key: o.key, nr, delta: o.delta, text: o.text });
});
const frei = num($('#aas-frei').val());
if (frei) list.push({ key: 'frei', nr: 0, delta: BIL.clamp(frei, -5, 5), text: $('#aas-frei-txt').val() });
return list;
}
function updateAasSumme() {
const list = readAas(); const s = BIL.aasSumme(list);
const bt = byBtc(aasCtx.rec[aasCtx.btField]); const base = aasCtx.bestandSeite ? bt.bw : bt.pw;
const eff = BIL.clamp(base + s, FK.wp_min, FK.wp_max);
$('#aas-summe').html('Summe <b>' + signed(s) + '</b> → Wert <b>' + eff + ' WP/m²</b>' + (Math.abs(s) > FK.aas_max_abs ? ' <span class="warn">(Einzelabschlag max. ±5, Wert 0–30)</span>' : ''));
}
$('#aas-body').on('change', 'input', updateAasSumme);
$('#aas-reset').on('click', () => { $('#aas-body input[type=radio][value=""]').prop('checked', true); $('#aas-frei').val(''); $('#aas-frei-txt').val(''); updateAasSumme(); });
$('#aas-ok').on('click', () => {
aasCtx.rec[aasCtx.aasField] = readAas();
bootstrap.Modal.getOrCreateInstance('#modal-aas').hide();
aasCtx.$card.find('[data-aas="' + aasCtx.aasField + '"]').text(aasText(aasCtx.rec[aasCtx.aasField]));
refreshCalc(); scheduleSave();
});
/* ------------------------------------------------- Biotoptyp-Info */
function showBtInfo(btc) {
const t = byBtc(btc); if (!t) return;
const kag = (t.kag || '').split('/').map(k => '<li><b>' + k + '</b> – ' + esc(FK.kag[k] || '') + '</li>').join('');
const aasB = FK.aas.filter(a => t.aas_b.includes(a.nr)).map(a => a.nr + ' ' + a.titel).join('; ') || 'keine';
const aasP = FK.aas.filter(a => t.aas_p.includes(a.nr)).map(a => a.nr + ' ' + a.titel).join('; ') || 'keine';
$('#bt-title').text(t.btc + ' ' + t.name);
$('#bt-body').html(
'<table class="table table-sm mb-2"><tr><th>Biotopwert (Bestand)</th><td>' + (t.bw ?? '–') + ' WP/m² ' + (t.bw_alt !== null && t.bw_alt !== undefined ? '(alt. ' + t.bw_alt + ')' : '') + ' – ' + esc(BIL.stufe(t.bw)) + '</td></tr>' +
'<tr><th>Planungswert (Ziel)</th><td>' + (t.pw ?? '–') + ' WP/m² ' + (t.pw_alt !== null && t.pw_alt !== undefined ? '(alt. ' + t.pw_alt + ')' : '') + '</td></tr>' +
'<tr><th>Kategorie Ausgleichbarkeit</th><td>' + (t.kag || '–') + '<ul class="mb-0">' + kag + '</ul></td></tr>' +
'<tr><th>Gesetzlicher Schutz</th><td>' + esc(t.schutz) + ' <span class="text-muted">(§ grundsätzlich, (§) bestimmte Ausprägungen, ((§)) bei Lage im Überschwemmungsbereich)</span></td></tr>' +
'<tr><th>FFH-LRT</th><td>' + esc(t.lrt) + '</td></tr><tr><th>Rote Liste Sachsen</th><td>' + esc(t.rl) + '</td></tr>' +
'<tr><th>AAS Bestand</th><td>' + esc(aasB) + '</td></tr><tr><th>AAS Planung</th><td>' + esc(aasP) + '</td></tr>' +
(t.btlnk ? '<tr><th>BTLNK / SBK</th><td class="text-muted">' + esc(t.btlnk) + ' / ' + esc(t.sbk) + '</td></tr>' : '') + '</table>' +
(t.definition ? '<p><b>Definition (Anlage 2):</b> ' + esc(t.definition) + '</p>' : '<p class="text-muted">Definition siehe Biotoptypenliste Sachsen (LfUG 2004 / Buder &amp; Uhlemann 2010).</p>')
);
bootstrap.Modal.getOrCreateInstance('#modal-bt').show();
}
/* ----------------------------------------------------- Meta-Daten */
function renderMeta() {
$('[data-meta]').each(function () {
const k = $(this).data('meta');
if (this.type === 'checkbox') this.checked = !!P.meta[k]; else $(this).val(P.meta[k] ?? '');
});
$('#liste-einfachfaelle').html(FK.einfachfaelle.map(e => '<li>' + esc(e) + '</li>').join(''));
}
$(document).on('change input', '[data-meta]', function () {
const k = $(this).data('meta');
P.meta[k] = this.type === 'checkbox' ? this.checked : this.value;
scheduleSave();
if (k === 'einfachfall' || k === 'fall_b') refreshCalc();
});
/* ----------------------------------------------- Eingriffsflächen */
function findEF(id) { return P.eingriff.flaechen.find(f => f.id == id); }
function efCard(f) {
const istBaum = !!f.kronen_dm || (KARTE.getFeature(f.fid) && KARTE.getFeature(f.fid).get('rolle') === 'baum');
return '<div class="card card-ef" data-kind="ef" data-id="' + f.id + '">' +
'<div class="card-body">' +
'<div class="d-flex align-items-center gap-2 mb-1"><span class="badge text-bg-danger">E' + f.id + '</span>' +
'<input class="form-control form-control-sm" data-f="bez" value="' + esc(f.bez) + '" placeholder="Bezeichnung der Fläche">' +
'<div class="btn-group btn-group-sm ms-auto flex-shrink-0">' +
'<button class="btn btn-outline-secondary btn-zoom" title="In Karte zeigen"' + (f.fid ? '' : ' disabled') + '><i class="bi bi-crosshair"></i></button>' +
'<button class="btn btn-outline-secondary btn-redraw" title="Fläche (neu) zeichnen"><i class="bi bi-pentagon"></i></button>' +
'<button class="btn btn-outline-danger btn-del" title="Fläche löschen"><i class="bi bi-trash"></i></button></div></div>' +
'<div class="row g-2">' +
'<div class="col-6"><label class="form-label">Bestand (vor Eingriff)</label>' + btSelect('bt_bestand', f.bt_bestand, 'bw').replace('<select ', '<select data-value="' + esc(f.bt_bestand || '') + '" ') +
'<div class="d-flex align-items-center gap-1 mt-1"><button class="btn btn-xs btn-outline-secondary btn-aas" data-bt="bt_bestand" data-aas-field="aas_bestand" data-bestand="1">AAS <span data-aas="aas_bestand">' + aasText(f.aas_bestand) + '</span></button>' +
'<button class="btn btn-xs btn-outline-secondary btn-btinfo" data-bt="bt_bestand" title="Details zum Biotoptyp"><i class="bi bi-info-circle"></i></button>' +
'<span class="ms-auto small">BW <b class="badge text-bg-light wert-badge" data-w="bw">–</b></span></div></div>' +
'<div class="col-6"><label class="form-label">Zustand nach Eingriff</label>' + btSelect('bt_planung', f.bt_planung, 'pw').replace('<select ', '<select data-value="' + esc(f.bt_planung || '') + '" ') +
'<div class="d-flex align-items-center gap-1 mt-1"><button class="btn btn-xs btn-outline-secondary btn-aas" data-bt="bt_planung" data-aas-field="aas_planung">AAS <span data-aas="aas_planung">' + aasText(f.aas_planung) + '</span></button>' +
'<button class="btn btn-xs btn-outline-secondary btn-btinfo" data-bt="bt_planung" title="Details zum Biotoptyp"><i class="bi bi-info-circle"></i></button>' +
'<span class="ms-auto small">PW <b class="badge text-bg-light wert-badge" data-w="pw">–</b></span></div></div></div>' +
'<div class="d-flex align-items-center gap-2 mt-2 small flex-wrap">' +
(istBaum ? '<label class="form-label mb-0">Kronen-Ø</label><div class="input-group input-group-sm" style="width:7rem"><input type="number" class="form-control" data-f="kronen_dm" min="0" step="0.5" value="' + esc(f.kronen_dm || '') + '"><span class="input-group-text">m</span></div>' : '') +
'<label class="form-label mb-0">Fläche</label><div class="input-group input-group-sm" style="width:12rem"><input type="number" class="form-control" data-f="gbf" min="0" step="1" value="' + esc(f.gbf ?? '') + '"><span class="input-group-text">m²</span>' +
'<button class="btn btn-outline-secondary btn-gbf-map" title="Fläche aus Karte übernehmen"' + (f.fid ? '' : ' disabled') + '><i class="bi bi-arrow-repeat"></i></button></div>' +
'<span class="text-muted" data-w="gbfinfo">' + (f.fid ? (f.gbf_manuell ? 'manuell' : 'aus Karte') : 'ohne Geometrie') + '</span>' +
'<span class="ms-auto">WP Bestand <b data-w="wpb">0</b> · nach Eingriff <b data-w="wpp">0</b> · Wertminderung <b data-w="diff">0</b></span></div>' +
'</div></div>';
}
function renderEF() {
const $l = $('#list-ef').empty();
if (!P.eingriff.flaechen.length) $l.html('<div class="text-muted small border rounded p-2">Noch keine Eingriffsflächen. Fläche in der Karte zeichnen oder Zeile anlegen.</div>');
P.eingriff.flaechen.forEach(f => $l.append(efCard(f)));
initSelects($l);
}
function addEF(fid, rolle) {
const f = { id: uid(), fid: fid || null, bez: '', bt_bestand: '', aas_bestand: [], bt_planung: '', aas_planung: [], gbf: '', gbf_manuell: false, kronen_dm: rolle === 'baum' ? 8 : '' };
P.eingriff.flaechen.push(f);
if (fid) { KARTE.setProps(fid, { label: 'E' + f.id, ref: 'ef:' + f.id, kronen_dm: f.kronen_dm || undefined }); syncArea(f); }
renderEF(); refreshCalc(); scheduleSave();
return f;
}
/* ------------------------------------------- Funktionen (Eingriff) */
function findFK(id) { return P.eingriff.funktionen.find(f => f.id == id); }
function fkCard(k) {
return '<div class="card card-fk" data-kind="fk" data-id="' + k.id + '"><div class="card-body">' +
'<div class="d-flex align-items-center gap-2 mb-1"><span class="badge text-bg-warning text-dark">F' + k.id + '</span>' +
'<select class="form-select form-select-sm" data-f="funktion">' + funkOpts(k.funktion, FK.funktionen) + '</select>' +
'<div class="btn-group btn-group-sm ms-auto flex-shrink-0">' +
'<button class="btn btn-outline-secondary btn-zoom" title="In Karte zeigen"' + (k.fid ? '' : ' disabled') + '><i class="bi bi-crosshair"></i></button>' +
'<button class="btn btn-outline-secondary btn-redraw" title="Funktionsraum (neu) zeichnen"><i class="bi bi-pentagon"></i></button>' +
'<button class="btn btn-outline-danger btn-del"><i class="bi bi-trash"></i></button></div></div>' +
'<div class="text-muted small mb-1" data-w="funkkurz"></div>' +
'<div class="row g-2 align-items-end">' +
'<div class="col-3"><label class="form-label">Einwirkungsbereich</label><select class="form-select form-select-sm" data-f="ewb">' + ewbOpts(k.ewb) + '</select></div>' +
'<div class="col-3"><label class="form-label">Bewertungsstufe</label><select class="form-select form-select-sm" data-f="bsf">' + stufenOpts(k.bsf) + '</select></div>' +
'<div class="col-3"><label class="form-label">Betroffenheit</label><select class="form-select form-select-sm" data-f="afb">' + afbOpts(k.afb, ['FV', 'FS', 'FH', 'FE']) + '</select></div>' +
'<div class="col-3"><label class="form-label">Faktor (Tab. 4-1)</label><div class="input-group input-group-sm"><span class="input-group-text" data-w="faktor">–</span><input type="number" class="form-control" data-f="faktor_override" step="0.1" min="0" max="2" placeholder="abw." value="' + esc(k.faktor_override ?? '') + '" title="Abweichender Faktor (Begründung erforderlich)"></div></div>' +
'</div>' +
'<div class="d-flex align-items-center gap-2 mt-2 small flex-wrap"><label class="form-label mb-0">Grundfläche Funktionsraum</label>' +
'<div class="input-group input-group-sm" style="width:12rem"><input type="number" class="form-control" data-f="gbf" min="0" step="1" value="' + esc(k.gbf ?? '') + '"><span class="input-group-text">m²</span><button class="btn btn-outline-secondary btn-gbf-map" title="Fläche aus Karte übernehmen"' + (k.fid ? '' : ' disabled') + '><i class="bi bi-arrow-repeat"></i></button></div>' +
'<input class="form-control form-control-sm" data-f="bemerkung" style="max-width:16rem" placeholder="Begründung / Datenquelle" value="' + esc(k.bemerkung || '') + '">' +
'<span class="ms-auto"><span data-w="seite"></span> <b data-w="wp">0</b> WP</span></div>' +
'</div></div>';
}
function renderFK() {
const $l = $('#list-fk').empty();
if (!P.eingriff.funktionen.length) $l.html('<div class="text-muted small border rounded p-2">Nur bei Betroffenheit von Werten und Funktionen besonderer Bedeutung (Stufen mittel bis sehr hoch).</div>');
P.eingriff.funktionen.forEach(k => $l.append(fkCard(k)));
}
function addFK(fid) {
const k = { id: uid(), fid: fid || null, funktion: FK.funktionen[0].key, ewb: 'UE', bsf: 'hoch', afb: 'FV', gbf: '', gbf_manuell: false, faktor_override: '', bemerkung: '' };
P.eingriff.funktionen.push(k);
if (fid) { KARTE.setProps(fid, { label: 'F' + k.id, ref: 'fk:' + k.id }); syncArea(k); }
renderFK(); refreshCalc(); scheduleSave();
return k;
}
/* ------------------------------------------------- Landschaftsbild */
function aufwRows(list, kind) {
return list.map(a => '<div class="faf-row row g-1 align-items-end" data-kind="' + kind + '" data-id="' + a.id + '">' +
'<div class="col-3"><input class="form-control form-control-sm" data-f="bez" placeholder="Maßnahme / Element" value="' + esc(a.bez || '') + '"></div>' +
'<div class="col-2"><select class="form-select form-select-sm" data-f="bsf">' + stufenOpts(a.bsf) + '</select></div>' +
'<div class="col-2"><select class="form-select form-select-sm" data-f="afb">' + afbOpts(a.afb, ['FH', 'FE']) + '</select></div>' +
'<div class="col-2"><div class="input-group input-group-sm"><input type="number" class="form-control" data-f="gbf" placeholder="m²" value="' + esc(a.gbf ?? '') + '"></div></div>' +
'<div class="col-2"><div class="input-group input-group-sm"><span class="input-group-text" data-w="faktor">–</span><input type="number" class="form-control" data-f="faktor_override" step="0.1" placeholder="abw." value="' + esc(a.faktor_override ?? '') + '"></div></div>' +
'<div class="col-1 text-end"><b data-w="wp">0</b><button class="btn btn-xs btn-outline-danger btn-del ms-1"><i class="bi bi-x"></i></button></div></div>').join('');
}
function renderLand() {
const L = P.eingriff.landschaft; const LB = FK.landschaftsbild;
const typ = LB.eingriffstypen.find(t => t.key === L.typ) || LB.eingriffstypen[0];
let h = '<div class="row g-2 align-items-end"><div class="col-12"><label class="form-label">Art des Eingriffs in das Landschaftsbild</label><select class="form-select form-select-sm" data-land="typ" data-rerender="1">' + optList(LB.eingriffstypen, L.typ, t => t.key, t => t.name) + '</select></div>';
if (typ.key !== 'keiner') {
const mast = typ.key.startsWith('mast');
h += '<div class="col-3"><label class="form-label">Gesamthöhe</label><div class="input-group input-group-sm"><input type="number" class="form-control" data-land="hoehe" min="0" step="0.5" value="' + esc(L.hoehe) + '"><span class="input-group-text">m</span></div></div>';
h += '<div class="col-3"><label class="form-label">Radius Nahzone (UE)</label><div class="input-group input-group-sm"><input type="number" class="form-control" data-land="radius1" min="' + (typ.radius1_min || 200) + '" step="10" placeholder="' + (typ.radius1 || 200) + '" value="' + esc(L.radius1) + '"' + (mast ? ' readonly' : '') + '><span class="input-group-text">m</span></div></div>';
if (typ.zone2) h += '<div class="col-3"><label class="form-label">Radius EE (25 × Höhe)</label><div class="input-group input-group-sm"><input type="number" class="form-control" data-land="radius2_override" min="0" step="10" placeholder="' + Math.round(BIL.clamp(LB.radius_faktor_hoehe * num(L.hoehe), 0, LB.radius_max)) + '" value="' + esc(L.radius2_override) + '"><span class="input-group-text">m</span></div></div>';
h += '<div class="col-3"><label class="form-label">Standort in der Karte</label><div class="btn-group btn-group-sm w-100"><button class="btn btn-outline-primary" id="btn-land-standort"><i class="bi bi-geo-alt"></i> ' + (L.standort_fid ? 'neu setzen' : 'setzen') + '</button><button class="btn btn-outline-secondary" id="btn-land-zoom"' + (L.standort_fid ? '' : ' disabled') + '><i class="bi bi-crosshair"></i></button></div></div>';
h += '</div>';
if (typ.key === 'mast_ab50') {
h += '<div class="mt-3 fw-semibold">Ersatzzahlung (F12) – Prozentsätze der Baukosten nach Bewertungsstufe des Landschaftsbildes</div>' +
'<div class="row g-2 align-items-end mt-1"><div class="col-4"><label class="form-label">Baukosten gesamt (DIN 276)</label><div class="input-group input-group-sm"><input type="number" class="form-control" data-land="baukosten" min="0" step="1000" value="' + esc(L.baukosten) + '"><span class="input-group-text">€</span></div></div>' +
'<div class="col-8"><label class="form-label">Repowering</label><select class="form-select form-select-sm" data-land="repowering">' + optList(LB.repowering, L.repowering, r => r.key, r => r.name) + '</select></div></div>' +
'<table class="table table-sm mt-2 mb-1"><thead><tr><th>Bewertungsstufe</th><th class="text-end">Anteil am Einwirkungsbereich %</th><th class="text-end">Kostensatz</th><th class="text-end">Ersatzzahlung</th></tr></thead><tbody>';
LB.stufen_ersatzzahlung.forEach(s => { h += '<tr><td>' + s + '</td><td><input type="number" class="form-control form-control-sm text-end" data-land="abl.' + s + '" min="0" max="100" step="1" value="' + esc((L.abl || {})[s] ?? '') + '"></td><td class="text-end">' + LB.ersatzzahlung_prozent[s] + ' %</td><td class="text-end" data-w="esz-' + s.replace(' ', '_') + '">–</td></tr>'; });
h += '</tbody></table><div class="d-flex"><span data-w="ablsum" class="text-muted"></span><span class="ms-auto">Ersatzzahlung gesamt (E1): <b class="wp-big" data-w="E1">0 €</b></span></div>';
} else {
h += '<div class="mt-3 fw-semibold">Wirkzonen – Anteile der Bewertungsstufen (Anlage 3) an der Zonenfläche</div><div class="text-muted">Wertpunkte je m²: ' + fmtF(typ.wpk1) + ' (Zone 1)' + (typ.zone2 ? ', ' + fmtF(typ.wpk2) + ' (Zone 2)' : '') + '; Funktionssenkungsfaktor sehr hoch 1,5 · hoch 1,0 · mittel 0,5. Stufen gering/sehr gering lösen keinen Bedarf aus.</div>' +
'<table class="table table-sm mt-1 mb-1"><thead><tr><th>Zone</th><th class="text-end">Zonenfläche m²</th><th class="text-end">sehr hoch %</th><th class="text-end">hoch %</th><th class="text-end">mittel %</th><th class="text-end">WP</th></tr></thead><tbody>';
[1, 2].filter(z => z === 1 || typ.zone2).forEach(z => {
const man = L['fwz' + z + '_manuell'];
h += '<tr><td data-w="zname' + z + '">Zone ' + z + '</td><td><div class="input-group input-group-sm"><input type="number" class="form-control text-end" data-land="fwz' + z + '" value="' + esc(L['fwz' + z]) + '"' + (man ? '' : ' readonly') + '><button class="btn btn-outline-secondary btn-fwz-toggle" data-z="' + z + '" title="' + (man ? 'automatisch aus Radius' : 'manuell eingeben') + '"><i class="bi ' + (man ? 'bi-unlock' : 'bi-lock') + '"></i></button></div></td>' +
['sehr hoch', 'hoch', 'mittel'].map(s => '<td><input type="number" class="form-control form-control-sm text-end" data-land="anteile.' + z + '.' + s + '" min="0" max="100" step="1" value="' + esc(((L.anteile || {})[z] || {})[s] ?? '') + '"></td>').join('') +
'<td class="text-end" data-w="zwp' + z + '">0</td></tr>';
});
h += '</tbody></table><div class="text-end">Kompensationsbedarf Landschaftsbild A3: <b data-w="A3">0</b> WP</div>';
}
}
h += '<div class="mt-3 d-flex align-items-center"><span class="fw-semibold">Aufwertung der landschaftsästhetischen Funktion im Planungszustand (P3)</span><button class="btn btn-xs btn-outline-primary ms-auto" id="btn-land-aufw"><i class="bi bi-plus-lg"></i> Zeile</button></div>' +
'<div id="list-land-aufw">' + aufwRows(L.aufwertungen, 'la') + '</div>';
$('#box-land').html(h);
}
$(document).on('change', '[data-land]', function () {
const path = $(this).data('land').split('.');
let o = P.eingriff.landschaft;
for (let i = 0; i < path.length - 1; i++) { o[path[i]] = o[path[i]] || {}; o = o[path[i]]; }
o[path[path.length - 1]] = this.type === 'checkbox' ? this.checked : this.value;
if ($(this).data('rerender')) renderLand();
updateZonen(); refreshCalc(); scheduleSave();
});
$(document).on('click', '.btn-fwz-toggle', function () {
const z = $(this).data('z'); const L = P.eingriff.landschaft;
L['fwz' + z + '_manuell'] = !L['fwz' + z + '_manuell'];
if (!L['fwz' + z + '_manuell']) L['fwz' + z] = '';
renderLand(); refreshCalc(); scheduleSave();
});
$(document).on('click', '#btn-land-standort', () => { startDraw({ kind: 'standort' }, 'standort', 'Point'); });
$(document).on('click', '#btn-land-zoom', () => KARTE.zoomTo(P.eingriff.landschaft.standort_fid));
$(document).on('click', '#btn-land-aufw', () => { P.eingriff.landschaft.aufwertungen.push({ id: uid(), bez: '', bsf: 'mittel', afb: 'FH', gbf: '', faktor_override: '' }); renderLand(); refreshCalc(); scheduleSave(); });
function updateZonen() {
const L = P.eingriff.landschaft;
if (!L.standort_fid || L.typ === 'keiner') { KARTE.clearZonen(); return; }
const land = BIL.calcLandschaft(L);
KARTE.setZonen(L.standort_fid, land.radius1 || 0, land.radius2 || 0);
}
/* --------------------------------------------------- Maßnahmen */
function findM(id) { return P.massnahmen.find(m => m.id == id); }
function typInfo(key) { return FK.massnahmentypen.find(t => t.key === key) || FK.massnahmentypen[0]; }
function paramsHtml(m) {
const p = m.params || {};
switch (m.typ) {
case 'F2':
return '<div class="form-check form-check-inline small"><input class="form-check-input" type="checkbox" data-f="params.baum" data-rerender="1" id="baum-' + m.id + '"' + (p.baum ? ' checked' : '') + '><label class="form-check-label" for="baum-' + m.id + '">Baumpflanzung nach Anlage 6.1 (pauschale Kronentrauffläche)</label></div>' +
(p.baum ? '<div class="row g-2 mt-1"><div class="col-4"><label class="form-label">Anzahl Bäume</label><input type="number" class="form-control form-control-sm" data-f="params.anzahl" min="0" step="1" value="' + esc(p.anzahl ?? '') + '"></div><div class="col-8"><label class="form-label">Baumart</label><select class="form-select form-select-sm" data-f="params.baumart"><option value="laub"' + (p.baumart !== 'obst' ? ' selected' : '') + '>Laubbaum Hochstamm – 30 m² je Baum</option><option value="obst"' + (p.baumart === 'obst' ? ' selected' : '') + '>Obstbaum Hochstamm – 20 m² je Baum</option></select></div></div>' : '');
case 'F3':
return '<label class="form-label">Kategorie der Bauwerkshöhe → Bonusfaktor Entsiegelung</label><select class="form-select form-select-sm" data-f="params.kbh">' + optList(FK.entsiegelung.kbh, p.kbh ?? 0, k => k.kbh, k => k.name + ' – BFE ' + fmtF(k.bfe)) + '</select><div class="text-muted small mt-1">' + esc(FK.entsiegelung.hinweis) + '</div>';
case 'F4': {
const pik = FK.pik.massnahmen.find(x => x.key === p.pik) || FK.pik.massnahmen[0];
return '<label class="form-label">PIK-Maßnahme (Steckbrief Anlage 6.4)</label><select class="form-select form-select-sm" data-f="params.pik" data-rerender="1">' + optList(FK.pik.massnahmen.filter(x => x.formblatt === 'F4'), pik.key, x => x.key, x => x.nr + ' – ' + x.name + ' (BFP ' + fmtF(x.bfp) + ')') + '</select>' +
'<div class="d-flex gap-3 mt-1 small">' + (pik.zusatz ? '<div class="form-check"><input class="form-check-input" type="checkbox" data-f="params.zfk" id="zfk-' + m.id + '"' + (p.zfk ? ' checked' : '') + '><label class="form-check-label" for="zfk-' + m.id + '">Kombination ≥ 2 PIK (+0,2)</label></div><div class="form-check"><input class="form-check-input" type="checkbox" data-f="params.zfz" id="zfz-' + m.id + '"' + (p.zfz ? ' checked' : '') + '><label class="form-check-label" for="zfz-' + m.id + '">Zusatzanforderung erfüllt (+0,2)</label></div>' : '<span class="text-muted">keine Bonuserhöhung möglich</span>') +
(pik.bfp_max ? '<div class="input-group input-group-sm ms-auto" style="width:9rem"><span class="input-group-text">BFP</span><input type="number" class="form-control" data-f="params.bfp_override" step="0.1" min="' + pik.bfp + '" max="' + pik.bfp_max + '" placeholder="' + pik.bfp + '" value="' + esc(p.bfp_override ?? '') + '"></div>' : '') + '</div>' +
(pik.ausgang.length ? '<div class="text-muted small mt-1">Ausgang: ' + pik.ausgang.join(', ') + (pik.ziel.length ? ' · Ziel: ' + pik.ziel.join(', ') : ' · Ziel: nicht degenerierter Pendant-Biotoptyp') + '</div>' : '');
}
case 'F5': {
const pik = FK.pik.massnahmen.find(x => x.key === p.pik) || FK.pik.massnahmen.find(x => x.formblatt === 'F5');
return '<label class="form-label">Artenschutz-PIK</label><select class="form-select form-select-sm" data-f="params.pik" data-rerender="1">' + optList(FK.pik.massnahmen.filter(x => x.formblatt === 'F5'), pik.key, x => x.key, x => x.name + ' (BFP ' + fmtF(x.bfp) + ')') + '</select>' +
'<div class="d-flex gap-3 mt-1 small align-items-center"><div class="form-check"><input class="form-check-input" type="checkbox" data-f="params.zfk" id="zfk-' + m.id + '"' + (p.zfk ? ' checked' : '') + '><label class="form-check-label" for="zfk-' + m.id + '">Kombination mit PIK 1–4 (+0,2)</label></div>' +
(pik.flaeche_je_einheit ? '<div class="input-group input-group-sm ms-auto" style="width:13rem"><span class="input-group-text">Lerchenfenster</span><input type="number" class="form-control" data-f="params.einheiten" min="0" step="1" value="' + esc(p.einheiten ?? '') + '"></div>' : '') + '</div><div class="text-muted small mt-1">Bezug: ' + esc(pik.bezug) + '. Kein Biotoptypwechsel; Bewertung = Bezugsfläche × Bonusfaktor.</div>';
}
case 'F6': {
return '<div class="row g-2"><div class="col-7"><label class="form-label">Kategorie</label><select class="form-select form-select-sm" data-f="params.kwv">' + optList(FK.wiedervernetzung.kategorien, p.kwv || '1', k => k.key, k => k.name) + '</select></div><div class="col-5"><label class="form-label">Form</label><select class="form-select form-select-sm" data-f="params.form">' + optList(Object.keys(FK.wiedervernetzung.formen), p.form || 'flaechig', k => k, k => FK.wiedervernetzung.formen[k]) + '</select></div></div><div class="text-muted small mt-1">Zusätzlich Funktionsherstellung Biotopverbund (FAF nach Bewertungsstufe) auf der Zielfläche eintragen; Retention und bioklimatischer Ausgleich nicht zulässig.</div>';
}
case 'F7': {
return '<div class="row g-2 align-items-end">' +
'<div class="col-4"><label class="form-label">Fläche Querungsanlage (UE)</label><div class="input-group input-group-sm"><input type="number" class="form-control" data-f="params.gbf_ue" min="0" value="' + esc(p.gbf_ue ?? '') + '"><span class="input-group-text">m²</span></div></div>' +
'<div class="col-8"><div class="form-check form-check-inline small mt-3"><input class="form-check-input" type="checkbox" data-f="params.gewaesser" data-rerender="1" id="gew-' + m.id + '"' + (p.gewaesser ? ' checked' : '') + '><label class="form-check-label" for="gew-' + m.id + '">gewässergebunden wandernde Zielart (Fischotter …)</label></div>' +
(!p.gewaesser ? '<div class="form-check form-check-inline small"><input class="form-check-input" type="checkbox" data-f="params.grossraeumig" id="gross-' + m.id + '"' + (p.grossraeumig ? ' checked' : '') + '><label class="form-check-label" for="gross-' + m.id + '">großräumig wandernd</label></div>' : '') + '</div>' +
'<div class="col-3"><label class="form-label">BQ Breite Querung</label><div class="input-group input-group-sm"><input type="number" class="form-control" data-f="params.bq" min="0" value="' + esc(p.bq ?? '') + '"><span class="input-group-text">m</span></div></div>' +
(p.gewaesser ? '<div class="col-3"><label class="form-label">hS hindernisfreie Strecke</label><div class="input-group input-group-sm"><input type="number" class="form-control" data-f="params.hs" min="0" max="4000" value="' + esc(p.hs ?? '') + '"><span class="input-group-text">m</span></div></div>' :
'<div class="col-3"><label class="form-label">LLZ Länge Leiteinrichtung</label><div class="input-group input-group-sm"><input type="number" class="form-control" data-f="params.llz" min="0" value="' + esc(p.llz ?? '') + '"><span class="input-group-text">m</span></div></div><div class="col-3"><label class="form-label">LKB Korridor bis Barriere</label><div class="input-group input-group-sm"><input type="number" class="form-control" data-f="params.lkb" min="0" value="' + esc(p.lkb ?? '') + '"><span class="input-group-text">m</span></div></div>') +
'<div class="col-3"><label class="form-label">p Anteil geeigneter Habitate</label><div class="input-group input-group-sm"><input type="number" class="form-control" data-f="params.p" min="0" max="100" value="' + esc(p.p ?? '') + '"><span class="input-group-text">%</span></div></div>' +
'<div class="col-12 small">anzurechnende Fläche EE aF = <b data-w="af">0</b> m² · <span class="text-muted">' + esc(FK.wiedervernetzung.technisch.hinweis) + '</span></div></div>';
}
case 'F8': {
const grm = FK.fliessgewaesser.massnahmen.find(x => x.key === p.grm) || FK.fliessgewaesser.massnahmen[0];
return '<div class="row g-2 align-items-end"><div class="col-8"><label class="form-label">Gewässerrevitalisierungsmaßnahme (Anlage 6.6)</label><select class="form-select form-select-sm" data-f="params.grm" data-rerender="1">' + optList(FK.fliessgewaesser.massnahmen, grm.key, x => x.key, x => x.key + ' ' + x.name) + '</select></div>' +
'<div class="col-4"><label class="form-label">Gewässertyp</label><select class="form-select form-select-sm" data-f="params.gewtyp"><option value="bach"' + (p.gewtyp !== 'fluss' ? ' selected' : '') + '>Bach</option><option value="fluss"' + (p.gewtyp === 'fluss' ? ' selected' : '') + '>Fluss</option></select></div>' +
(grm.bonus ? '<div class="col-12"><div class="form-check small"><input class="form-check-input" type="checkbox" data-f="params.bonus" id="bfr-' + m.id + '"' + (p.bonus !== false ? ' checked' : '') + '><label class="form-check-label" for="bfr-' + m.id + '">Bonusfaktor Gewässerrevitalisierung BFR ' + fmtF(FK.fliessgewaesser.bfr) + ' ansetzen' + (grm.bonus_hinweis ? ' – ' + esc(grm.bonus_hinweis) : '') + '</label></div></div>' : '') +
(grm.plus_wp ? '<div class="col-12 text-muted small">Planungswert = Ausgangsbiotoptyp + ' + grm.plus_wp + ' WP/m² (Zielbiotoptyp nicht erforderlich).</div>' : '') +
(grm.faf.length ? '<div class="col-12"><button class="btn btn-xs btn-outline-primary btn-grm-faf"><i class="bi bi-list-check"></i> Funktionsaufwertungsfaktoren des Steckbriefs übernehmen</button> <span class="text-muted small">' + grm.faf.map(f => FUNK_ALL.find(x => x.key === f.funktion).name + ' ' + f.afb + ' ' + fmtF(p.gewtyp === 'fluss' ? f.fluss : f.bach) + ' (' + f.bezug + ')').join('; ') + '</span></div>' : '') + '</div>';
}
case 'F9': {
const w = FK.wald.massnahmen.find(x => x.key === p.wald) || FK.wald.massnahmen[0];
return '<label class="form-label">Maßnahme im Wald (Anlage 6.7)</label><select class="form-select form-select-sm" data-f="params.wald" data-rerender="1">' + optList(FK.wald.massnahmen, w.key, x => x.key, x => x.name) + '</select>' +
'<div class="row g-2 mt-1 align-items-end"><div class="col-8"><label class="form-label">Bonusfaktor Wald (× Fläche)</label>' + (w.bfn_optionen ? '<select class="form-select form-select-sm" data-f="params.bfn">' + optList(w.bfn_optionen, p.bfn ?? w.bfn, o => o.bfn, o => fmtF(o.bfn) + ' – ' + o.text) + '</select>' : '<input type="number" class="form-control form-control-sm" data-f="params.bfn" step="0.5" value="' + esc(p.bfn ?? w.bfn) + '">') + '</div><div class="col-4 small text-muted">Ziel: ' + w.ziel.join(', ') + '</div></div><div class="text-muted small mt-1">' + esc(w.faf_hinweis) + '</div>';
}
case 'F10': {
const w = FK.weitere.massnahmen.find(x => x.key === p.art) || FK.weitere.massnahmen[0];
return '<div class="row g-2 align-items-end"><div class="col-8"><label class="form-label">Maßnahme (Anlage 6.8)</label><select class="form-select form-select-sm" data-f="params.art" data-rerender="1">' + optList(FK.weitere.massnahmen, w.key, x => x.key, x => x.name) + '</select></div>' +
'<div class="col-4"><label class="form-label">Bonusfaktor BFN (Regel ' + fmtF(w.bfn) + ', max. ' + fmtF(w.bfn_max) + ')</label><input type="number" class="form-control form-control-sm" data-f="params.bfn" step="0.5" min="0" max="' + w.bfn_max + '" placeholder="' + w.bfn + '" value="' + esc(p.bfn ?? '') + '"></div></div><div class="text-muted small mt-1">' + esc(FK.weitere.hinweis) + '</div>';
}
case 'F11':
return '<div class="row g-2"><div class="col-6"><label class="form-label">Fläche Ausgangszustand (sichtbare Oberfläche)</label><div class="input-group input-group-sm"><input type="number" class="form-control" data-f="params.gbf_a" min="0" value="' + esc(p.gbf_a ?? '') + '"><span class="input-group-text">m²</span></div></div><div class="col-6"><label class="form-label">Fläche Planungszustand</label><div class="input-group input-group-sm"><input type="number" class="form-control" data-f="params.gbf_p" min="0" value="' + esc(p.gbf_p ?? '') + '"><span class="input-group-text">m²</span></div></div></div><div class="text-muted small mt-1">' + esc(FK.trockenmauer.hinweis) + '</div>';
}
return '';
}
function funkRows(m) {
return (m.funktionen || []).map(k => '<div class="faf-row row g-1 align-items-end" data-kind="mf" data-id="' + k.id + '">' +
'<div class="col-4"><select class="form-select form-select-sm" data-f="funktion">' + funkOpts(k.funktion, FUNK_ALL) + '</select></div>' +
'<div class="col-1"><select class="form-select form-select-sm px-1" data-f="ewb"><option' + (k.ewb === 'UE' ? ' selected' : '') + '>UE</option><option' + (k.ewb === 'EE' ? ' selected' : '') + '>EE</option></select></div>' +
'<div class="col-2"><select class="form-select form-select-sm" data-f="bsf">' + stufenOpts(k.bsf) + '</select></div>' +
'<div class="col-1"><select class="form-select form-select-sm px-1" data-f="afb">' + optList(['FH', 'FE', 'FV', 'FS'], k.afb, s => s, s => s) + '</select></div>' +
'<div class="col-2"><div class="input-group input-group-sm"><input type="number" class="form-control" data-f="gbf" placeholder="m²" value="' + esc(k.gbf ?? '') + '"><button class="btn btn-outline-secondary btn-gbf-copy px-1" title="Maßnahmenfläche übernehmen"><i class="bi bi-arrow-down-left"></i></button></div></div>' +
'<div class="col-1"><input type="number" class="form-control form-control-sm px-1" data-f="faktor_override" step="0.1" placeholder="' + (BIL.faktorZeile(Object.assign({}, k, { faktor_override: '' })) ?? '') + '" value="' + esc(k.faktor_override ?? '') + '" title="Faktor (Tab. 4-1 oder Steckbrief)"></div>' +
'<div class="col-1 text-end text-nowrap"><b data-w="wp">0</b><button class="btn btn-xs btn-outline-danger btn-del ms-1"><i class="bi bi-x"></i></button></div>' +
(k.bez ? '<div class="col-12 text-muted small">' + esc(k.bez) + '</div>' : '') + '</div>').join('');
}
function mCard(m) {
const ti = typInfo(m.typ);
const p = m.params || {};
const ohneBT = m.typ === 'F5' || m.typ === 'F7';
const grm = m.typ === 'F8' ? FK.fliessgewaesser.massnahmen.find(x => x.key === p.grm) : null;
return '<div class="card card-m" data-kind="m" data-id="' + m.id + '"><div class="card-body">' +
'<div class="d-flex align-items-center gap-2 mb-1"><span class="badge text-bg-success">M' + m.id + '</span><span class="badge bg-secondary-subtle text-dark badge-typ">' + esc(ti.kurz) + '</span>' +
'<input class="form-control form-control-sm" data-f="name" value="' + esc(m.name) + '" placeholder="Bezeichnung der Kompensationsmaßnahme">' +
'<div class="btn-group btn-group-sm ms-auto flex-shrink-0"><button class="btn btn-outline-secondary btn-zoom" title="In Karte zeigen"' + (m.fid ? '' : ' disabled') + '><i class="bi bi-crosshair"></i></button><button class="btn btn-outline-secondary btn-redraw" title="Fläche (neu) zeichnen"><i class="bi bi-pentagon"></i></button><button class="btn btn-outline-danger btn-del"><i class="bi bi-trash"></i></button></div></div>' +
'<div class="mb-2">' + paramsHtml(m) + '</div>' +
(ohneBT ? '' : '<div class="row g-2">' +
'<div class="col-6"><label class="form-label">Ausgangszustand</label>' + btSelect('bt_ausgang', m.bt_ausgang, 'bw').replace('<select ', '<select data-value="' + esc(m.bt_ausgang || '') + '" ') +
'<div class="d-flex align-items-center gap-1 mt-1"><button class="btn btn-xs btn-outline-secondary btn-aas" data-bt="bt_ausgang" data-aas-field="aas_ausgang" data-bestand="1">AAS <span data-aas="aas_ausgang">' + aasText(m.aas_ausgang) + '</span></button><button class="btn btn-xs btn-outline-secondary btn-btinfo" data-bt="bt_ausgang"><i class="bi bi-info-circle"></i></button><span class="ms-auto small">BW <b class="badge text-bg-light wert-badge" data-w="bw">–</b></span></div></div>' +
'<div class="col-6"><label class="form-label">Zielzustand (nach 25 Jahren)</label>' + (grm && grm.plus_wp ? '<div class="form-control form-control-sm bg-light">Ausgangsbiotoptyp + ' + grm.plus_wp + ' WP/m²</div><div class="mt-1 small text-end">PW <b class="badge text-bg-light wert-badge" data-w="pw">–</b></div>' :
btSelect('bt_ziel', m.bt_ziel, 'pw').replace('<select ', '<select data-value="' + esc(m.bt_ziel || '') + '" ') +
'<div class="d-flex align-items-center gap-1 mt-1"><button class="btn btn-xs btn-outline-secondary btn-aas" data-bt="bt_ziel" data-aas-field="aas_ziel">AAS <span data-aas="aas_ziel">' + aasText(m.aas_ziel) + '</span></button><button class="btn btn-xs btn-outline-secondary btn-btinfo" data-bt="bt_ziel"><i class="bi bi-info-circle"></i></button><span class="ms-auto small">PW <b class="badge text-bg-light wert-badge" data-w="pw">–</b></span></div>') + '</div></div>') +
(m.typ === 'F11' || m.typ === 'F7' ? '' : '<div class="d-flex align-items-center gap-2 mt-2 small flex-wrap"><label class="form-label mb-0">' + (m.typ === 'F5' ? 'Bezugsfläche' : 'Maßnahmenfläche') + '</label><div class="input-group input-group-sm" style="width:12rem"><input type="number" class="form-control" data-f="gbf" min="0" step="1" value="' + esc(m.gbf ?? '') + '"' + (p.baum ? ' readonly' : '') + '><span class="input-group-text">m²</span><button class="btn btn-outline-secondary btn-gbf-map" title="Fläche aus Karte übernehmen"' + (m.fid ? '' : ' disabled') + '><i class="bi bi-arrow-repeat"></i></button></div><span class="text-muted" data-w="gbfinfo">' + (m.fid ? (m.gbf_manuell ? 'manuell' : 'aus Karte') : 'ohne Geometrie') + '</span><span class="ms-auto" data-w="s1info"></span></div>') +
'<div class="d-flex align-items-center mt-2"><span class="small fw-semibold">Auf-/Abwertung von Werten und Funktionen besonderer Bedeutung (Anlage 4)</span><button class="btn btn-xs btn-outline-primary ms-auto btn-mf-add"><i class="bi bi-plus-lg"></i> Funktion</button></div>' +
'<div class="small"><div class="row g-1 text-muted" style="font-size:.7rem"><div class="col-4">Funktion</div><div class="col-1">EWB</div><div class="col-2">Stufe</div><div class="col-1">Art</div><div class="col-2">Fläche m²</div><div class="col-1">Faktor</div><div class="col-1 text-end">WP</div></div><div class="list-mf">' + funkRows(m) + '</div></div>' +
'<div class="mt-2 small d-flex flex-wrap gap-3 border-top pt-1"><span>Biotoptypen S1 <b data-w="S1">0</b></span><span>Funktionen S2 <b data-w="S2">0</b></span><span>Abwertung S3 <b data-w="S3">0</b></span><span>Landschaft S4 <b data-w="S4">0</b></span><span class="ms-auto">L1 <b class="text-success" data-w="L1">0</b> · L2 <b class="text-success" data-w="L2">0</b> WP</span></div>' +
'<div class="small warn" data-w="warn"></div>' +
'</div></div>';
}
function renderM() {
const $l = $('#list-m').empty();
if (!P.massnahmen.length) $l.html('<div class="text-muted small border rounded p-2">Noch keine Kompensationsmaßnahmen. Maßnahmentyp wählen und Fläche zeichnen oder Zeile anlegen.</div>');
P.massnahmen.forEach(m => $l.append(mCard(m)));
initSelects($l);
$('#badge-komp').text(P.massnahmen.length);
}
function renderMCard(m) {
const $old = $('.card[data-kind="m"][data-id="' + m.id + '"]');
$old.find('select.bt-select').each(function () { try { $(this).selectpicker('destroy'); } catch (e) { } });
const $new = $(mCard(m)); $old.replaceWith($new); initSelects($new);
}
function addM(typ, fid) {
const m = { id: uid(), fid: fid || null, typ, name: '', bt_ausgang: '', aas_ausgang: [], bt_ziel: '', aas_ziel: [], gbf: '', gbf_manuell: false, params: {}, funktionen: [] };
// Voreinstellungen je Typ
if (typ === 'F4') { m.params.pik = '1'; m.bt_ausgang = '10.01.200'; }
if (typ === 'F5') { m.params.pik = '10a'; }
if (typ === 'F6') { m.params.kwv = '1'; m.params.form = 'flaechig'; }
if (typ === 'F8') { m.params.grm = '1a'; m.params.gewtyp = 'bach'; }
if (typ === 'F9') { m.params.wald = 'naturwaldzelle'; m.params.bfn = 3; m.bt_ziel = '01.11.200'; }
if (typ === 'F10') { m.params.art = 'moor'; }
if (typ === 'F11') { m.bt_ausgang = FK.trockenmauer.ausgang; m.bt_ziel = FK.trockenmauer.ziel; }
if (typ === 'F3') { m.params.kbh = 0; }
P.massnahmen.push(m);
if (fid) { KARTE.setProps(fid, { label: 'M' + m.id, ref: 'm:' + m.id }); syncArea(m); }
renderM(); refreshCalc(); scheduleSave();
return m;
}
/* ----------------------------------------------- generische Events */
function recOf($el) {
const $c = $el.closest('[data-kind]'); const kind = $c.data('kind'); const id = $c.data('id');
if (kind === 'ef') return { kind, rec: findEF(id), $card: $c };
if (kind === 'fk') return { kind, rec: findFK(id), $card: $c };
if (kind === 'm') return { kind, rec: findM(id), $card: $c };
if (kind === 'la') return { kind, rec: P.eingriff.landschaft.aufwertungen.find(a => a.id == id), $card: $c, parent: P.eingriff.landschaft };
if (kind === 'mf') { const $m = $c.closest('[data-kind="m"]'); const m = findM($m.data('id')); return { kind, rec: (m.funktionen || []).find(k => k.id == id), $card: $c, parent: m, $mcard: $m }; }
return null;
}
function setPath(obj, path, val) {
const parts = path.split('.'); let o = obj;
for (let i = 0; i < parts.length - 1; i++) { o[parts[i]] = o[parts[i]] || {}; o = o[parts[i]]; }
o[parts[parts.length - 1]] = val;
}
$(document).on('change', '#list-ef [data-f], #list-fk [data-f], #list-m [data-f], #list-land-aufw [data-f]', function () {
const r = recOf($(this)); if (!r || !r.rec) return;
const f = $(this).data('f');
const val = this.type === 'checkbox' ? this.checked : this.value;
setPath(r.rec, f, val);
if (f === 'gbf') r.rec.gbf_manuell = true;
if (f === 'kronen_dm' && r.rec.fid) { KARTE.setProps(r.rec.fid, { kronen_dm: num(val) }); KARTE.refresh(); r.rec.gbf_manuell = false; syncArea(r.rec); r.$card.find('[data-f="gbf"]').val(r.rec.gbf); }
if (f === 'bez' && r.rec.fid) { KARTE.setProps(r.rec.fid, { label: (r.kind === 'ef' ? 'E' : 'F') + r.rec.id + (val ? ' ' + val : '') }); KARTE.refresh(); }
if (f === 'name' && r.rec.fid) { KARTE.setProps(r.rec.fid, { label: 'M' + r.rec.id + (val ? ' ' + val : '') }); KARTE.refresh(); }
if (f.startsWith('bt_')) { // AAS bereinigen, die für den neuen Typ nicht zulässig sind
const aasField = f.replace('bt_', 'aas_'); const t = byBtc(val); const bestandSeite = (f === 'bt_bestand' || f === 'bt_ausgang');
if (t) { r.rec[aasField] = (r.rec[aasField] || []).filter(a => a.key === 'frei' || (bestandSeite ? t.aas_b : t.aas_p).includes(a.nr)); }
r.$card.find('[data-aas="' + aasField + '"]').text(aasText(r.rec[aasField]));
}
if (r.kind === 'm' && f.startsWith('params.') && r.rec.params.baum) {
const je = r.rec.params.baumart === 'obst' ? FK.einzelbaum.kronentrauf_obst_m2 : FK.einzelbaum.kronentrauf_laub_m2;
r.rec.gbf = num(r.rec.params.anzahl) * je; r.rec.gbf_manuell = true;
if (!r.rec.bt_ausgang) r.rec.bt_ausgang = FK.einzelbaum.ausgangs_btc;
}
if (r.kind === 'm' && f === 'params.pik') { const pik = FK.pik.massnahmen.find(x => x.key === val); if (pik && pik.ausgang && pik.ausgang.length === 1) r.rec.bt_ausgang = pik.ausgang[0]; if (pik && pik.ziel && pik.ziel.length === 1) r.rec.bt_ziel = pik.ziel[0]; }
if (r.kind === 'm' && f === 'params.wald') { const w = FK.wald.massnahmen.find(x => x.key === val); if (w) { r.rec.bt_ziel = w.ziel[0]; r.rec.params.bfn = w.bfn; } }
if (r.kind === 'm' && f === 'params.art') { const w = FK.weitere.massnahmen.find(x => x.key === val); if (w) r.rec.params.bfn = ''; }
if (r.kind === 'm' && f === 'params.gewtyp') { // Steckbrief-Faktoren neu setzen
(r.rec.funktionen || []).filter(k => k.grm).forEach(k => { const grm = FK.fliessgewaesser.massnahmen.find(x => x.key === r.rec.params.grm); const src = grm && grm.faf.find(x => x.funktion === k.funktion && x.afb === k.afb && x.bezug === k.bez); if (src) k.faktor_override = val === 'fluss' ? src.fluss : src.bach; });
}
if ($(this).data('rerender')) { if (r.kind === 'm') renderMCard(r.rec); }
if (r.kind === 'mf' && f === 'funktion') { }
refreshCalc(); scheduleSave();
});
$(document).on('click', '.btn-del', function () {
const r = recOf($(this)); if (!r || !r.rec) return;
if (r.kind === 'ef') { if (r.rec.fid) KARTE.removeFeature(r.rec.fid); P.eingriff.flaechen = P.eingriff.flaechen.filter(x => x !== r.rec); renderEF(); }
else if (r.kind === 'fk') { if (r.rec.fid) KARTE.removeFeature(r.rec.fid); P.eingriff.funktionen = P.eingriff.funktionen.filter(x => x !== r.rec); renderFK(); }
else if (r.kind === 'm') { if (!confirm('Maßnahme M' + r.rec.id + ' löschen?')) return; if (r.rec.fid) KARTE.removeFeature(r.rec.fid); P.massnahmen = P.massnahmen.filter(x => x !== r.rec); renderM(); }
else if (r.kind === 'la') { r.parent.aufwertungen = r.parent.aufwertungen.filter(x => x !== r.rec); renderLand(); }
else if (r.kind === 'mf') { r.parent.funktionen = r.parent.funktionen.filter(x => x !== r.rec); r.$card.remove(); }
refreshCalc(); scheduleSave();
});
$(document).on('click', '.btn-zoom', function () { const r = recOf($(this)); if (r && r.rec && r.rec.fid) { KARTE.zoomTo(r.rec.fid); KARTE.selectFeature(r.rec.fid); } });
$(document).on('click', '.btn-redraw', function () {
const r = recOf($(this)); if (!r) return;
const rolle = r.kind === 'ef' ? 'eingriff' : r.kind === 'fk' ? 'funktionsraum' : 'kompensation';
startDraw({ kind: r.kind, id: r.rec.id }, rolle, 'Polygon');
});
$(document).on('click', '.btn-gbf-map', function () { const r = recOf($(this)); if (!r || !r.rec.fid) return; r.rec.gbf_manuell = false; syncArea(r.rec); r.$card.find('[data-f="gbf"]').val(r.rec.gbf); refreshCalc(); scheduleSave(); });
$(document).on('click', '.btn-gbf-copy', function () { const r = recOf($(this)); if (!r) return; r.rec.gbf = r.parent.gbf; r.$card.find('[data-f="gbf"]').val(r.rec.gbf); refreshCalc(); scheduleSave(); });
$(document).on('click', '.btn-aas', function () {
const r = recOf($(this)); if (!r) return;
openAas({ rec: r.rec, $card: r.$card, btField: $(this).data('bt'), aasField: $(this).data('aasField'), bestandSeite: !!$(this).data('bestand') });
});
$(document).on('click', '.btn-btinfo', function () { const r = recOf($(this)); if (r) showBtInfo(r.rec[$(this).data('bt')]); });
$(document).on('click', '.btn-mf-add', function () {
const r = recOf($(this)); if (!r) return;
r.rec.funktionen = r.rec.funktionen || [];
r.rec.funktionen.push({ id: uid(), funktion: FUNK_ALL[0].key, ewb: 'UE', bsf: 'mittel', afb: 'FH', gbf: r.rec.gbf || '', faktor_override: '', bez: '' });
r.$card.find('.list-mf').html(funkRows(r.rec)); refreshCalc(); scheduleSave();
});
$(document).on('click', '.btn-grm-faf', function () {
const r = recOf($(this)); if (!r) return; const p = r.rec.params;
const grm = FK.fliessgewaesser.massnahmen.find(x => x.key === p.grm); if (!grm) return;
r.rec.funktionen = (r.rec.funktionen || []).filter(k => !k.grm);
grm.faf.forEach(f => r.rec.funktionen.push({ id: uid(), grm: grm.key, funktion: f.funktion, ewb: 'UE', bsf: 'hoch', afb: f.afb, gbf: r.rec.gbf || '', faktor_override: p.gewtyp === 'fluss' ? f.fluss : f.bach, bez: f.bezug }));
r.$card.find('.list-mf').html(funkRows(r.rec)); refreshCalc(); scheduleSave();
toast('Faktoren übernommen – Bezugsflächen je Zeile prüfen/eintragen.', 'primary');
});
$('#btn-ef-add').on('click', () => addEF(null));
$('#btn-ef-draw').on('click', () => startDraw({ kind: 'ef' }, 'eingriff', 'Polygon'));
$('#btn-fk-add').on('click', () => addFK(null));
$('#btn-fk-draw').on('click', () => startDraw({ kind: 'fk' }, 'funktionsraum', 'Polygon'));
$('#btn-m-add').on('click', () => addM($('#sel-mtyp').val(), null));
$('#btn-m-draw').on('click', () => startDraw({ kind: 'm', typ: $('#sel-mtyp').val() }, 'kompensation', 'Polygon'));
/* ------------------------------------------------- Karte ↔ Daten */
function syncArea(rec) {
if (!rec.fid) return;
const a = KARTE.area(rec.fid);
if (a !== null && !rec.gbf_manuell) rec.gbf = Math.round(a);
}
function startDraw(ctx, rolle, type) {
drawCtx = ctx;
KARTE.startDraw(rolle, type, ctx);
$('#map-hint').text(type === 'Point' ? 'Punkt in der Karte setzen (Esc = abbrechen).' : 'Fläche in der Karte zeichnen – Klick je Stützpunkt, Doppelklick beendet (Esc = abbrechen).');
$('#btn-draw-cancel').prop('hidden', false);
}
function onDrawEnd(feature, rolle, ctx) {
const fid = feature.get('fid');
ctx = ctx || drawCtx || {};
let rec = null;
if (ctx.kind === 'ef') {
rec = ctx.id ? findEF(ctx.id) : null;
if (rec) { if (rec.fid) KARTE.removeFeature(rec.fid); rec.fid = fid; rec.gbf_manuell = false; KARTE.setProps(fid, { label: 'E' + rec.id + (rec.bez ? ' ' + rec.bez : ''), ref: 'ef:' + rec.id, kronen_dm: rec.kronen_dm || undefined }); syncArea(rec); renderEF(); }
else rec = addEF(fid, rolle);
showTab('tab-eingriff');
} else if (ctx.kind === 'fk') {
rec = ctx.id ? findFK(ctx.id) : null;
if (rec) { if (rec.fid) KARTE.removeFeature(rec.fid); rec.fid = fid; rec.gbf_manuell = false; KARTE.setProps(fid, { label: 'F' + rec.id, ref: 'fk:' + rec.id }); syncArea(rec); renderFK(); }
else rec = addFK(fid);
showTab('tab-eingriff');
} else if (ctx.kind === 'm') {
rec = ctx.id ? findM(ctx.id) : null;
if (rec) { if (rec.fid) KARTE.removeFeature(rec.fid); rec.fid = fid; rec.gbf_manuell = false; KARTE.setProps(fid, { label: 'M' + rec.id + (rec.name ? ' ' + rec.name : ''), ref: 'm:' + rec.id }); syncArea(rec); renderM(); }
else rec = addM(ctx.typ || 'F2', fid);
showTab('tab-komp');
} else if (ctx.kind === 'standort') {
const L = P.eingriff.landschaft;
if (L.standort_fid) KARTE.removeFeature(L.standort_fid);
L.standort_fid = fid; KARTE.setProps(fid, { label: 'Standort', ref: 'land' }); updateZonen(); renderLand();
}
drawCtx = null;
$('#map-hint').text('Fläche gezeichnet. Stützpunkte mit dem Stift-Werkzeug bearbeiten.');
refreshCalc(); scheduleSave();
if (rec && rec.id) highlightCard(ctx.kind === 'm' ? 'm' : ctx.kind, rec.id);
}
function refOf(fid) {
const f = KARTE.getFeature(fid); if (!f) return null;
const ref = f.get('ref') || ''; const [kind, id] = ref.split(':');
return { kind, id };
}
function highlightCard(kind, id) {
$('.card.selected').removeClass('selected');
const $c = $('.card[data-kind="' + kind + '"][data-id="' + id + '"]');
if ($c.length) { $c.addClass('selected'); $c[0].scrollIntoView({ block: 'nearest', behavior: 'smooth' }); }
}
function onSelect(fid) {
selectedFid = fid;
$('.card.selected').removeClass('selected');
if (!fid) return;
const r = refOf(fid); if (!r) return;
if (r.kind === 'ef' || r.kind === 'fk') showTab('tab-eingriff');
if (r.kind === 'm') showTab('tab-komp');
if (r.kind === 'land') { showTab('tab-eingriff'); return; }
setTimeout(() => highlightCard(r.kind, r.id), 150);
}
function onModify(ids) {
ids.forEach(fid => {
const r = refOf(fid); if (!r) return;
const rec = r.kind === 'ef' ? findEF(r.id) : r.kind === 'fk' ? findFK(r.id) : r.kind === 'm' ? findM(r.id) : null;
if (rec) { syncArea(rec); $('.card[data-kind="' + r.kind + '"][data-id="' + rec.id + '"] [data-f="gbf"]').val(rec.gbf); }
if (r.kind === 'land') updateZonen();
});
refreshCalc(); scheduleSave();
}
function onDeleteGeometry(fid) {
const r = refOf(fid); if (!r) return;
const rec = r.kind === 'ef' ? findEF(r.id) : r.kind === 'fk' ? findFK(r.id) : r.kind === 'm' ? findM(r.id) : null;
if (!confirm('Geometrie in der Karte löschen? Der Eintrag bleibt mit manueller Fläche erhalten.')) return;
KARTE.removeFeature(fid);
if (rec) { rec.fid = null; rec.gbf_manuell = true; }
if (r.kind === 'land') { P.eingriff.landschaft.standort_fid = null; KARTE.clearZonen(); renderLand(); }
renderEF(); renderFK(); renderM(); refreshCalc(); scheduleSave();
}
$('#map-tools [data-tool]').on('click', function () {
$('#map-tools [data-tool]').removeClass('active'); $(this).addClass('active');
KARTE.setTool($(this).data('tool'));
$('#map-hint').text({ select: 'Auswählen: Klick auf eine Fläche wählt den Eintrag.', modify: 'Bearbeiten: Fläche anklicken, dann Stützpunkte ziehen.', delete: 'Löschen: Klick auf eine Fläche entfernt ihre Geometrie.' }[$(this).data('tool')]);
});
$('#btn-draw-cancel').on('click', () => { KARTE.cancelDraw(); });
$('#sel-basis').on('change', function () { KARTE.setBase(this.value); });
$('#chk-flst').on('change', function () { KARTE.setFlst(this.checked); });
$('#btn-suche').on('click', doSearch); $('#inp-suche').on('keydown', e => { if (e.key === 'Enter') doSearch(); });
function doSearch() {
const q = $('#inp-suche').val().trim(); if (!q) return;
KARTE.search(q).done(res => { if (!res.length) { toast('Kein Treffer.', 'warning'); return; } KARTE.centerLonLat(+res[0].lon, +res[0].lat, 11); toast(res[0].display_name, 'secondary'); }).fail(() => toast('Suche nicht erreichbar.', 'danger'));
}
/* ------------------------------------------------- Berechnungen */
function refreshCalc() {
const G = BIL.calcGesamt(P);
const f1 = G.f1;
// Eingriffsflächen
f1.bestand.forEach((b, i) => {
const p = f1.planung[i];
const $c = $('.card[data-kind="ef"][data-id="' + b.id + '"]');
$c.find('[data-w="bw"]').text(b.wert === null ? '–' : b.wert).attr('title', BIL.stufe(b.wert));
$c.find('[data-w="pw"]').text(p.wert === null ? '–' : p.wert);
$c.find('[data-w="wpb"]').text(fmtWP(b.wp)); $c.find('[data-w="wpp"]').text(fmtWP(p.wp)); $c.find('[data-w="diff"]').text(fmtWP(b.wp - p.wp));
});
f1.fkRows.forEach(k => {
const $c = $('.card[data-kind="fk"][data-id="' + k.id + '"]');
$c.find('[data-w="faktor"]').text(fmtF(BIL.isMinderung(k.afb) ? BIL.fmf(k.bsf, k.afb) : BIL.faf(k.bsf, k.afb)));
$c.find('[data-w="wp"]').text(fmtWP(k.wp)); $c.find('[data-w="seite"]').text(k.seite === 'A' ? 'Wertminderung A2' : 'Aufwertung P2');
const fk = FK.funktionen.find(x => x.key === k.funktion); $c.find('[data-w="funkkurz"]').text(fk ? fk.schutzgut + ' · ' + fk.kurz : '');
});
// Landschaftsbild
const land = f1.land; const $b = $('#box-land');
land.zonen.forEach(z => { $b.find('[data-w="zname' + z.nr + '"]').text(z.name); $b.find('[data-w="zwp' + z.nr + '"]').text(fmtWP(z.stufen.reduce((s, x) => s + x.wp, 0))); if (!P.eingriff.landschaft['fwz' + z.nr + '_manuell']) $b.find('[data-land="fwz' + z.nr + '"]').val(Math.round(z.fwz)); });
$b.find('[data-w="A3"]').text(fmtWP(land.A3));
if (land.ersatz) { land.ersatz.rows.forEach(r => $b.find('[data-w="esz-' + r.stufe.replace(' ', '_') + '"]').text(fmtEur(r.esz))); $b.find('[data-w="E1"]').text(fmtEur(land.ersatz.E1)); $b.find('[data-w="ablsum"]').text('Summe Anteile: ' + land.ersatz.ablSumme + ' %' + (Math.round(land.ersatz.ablSumme) !== 100 && land.ersatz.ablSumme ? ' (≠ 100 %)' : '') + (land.ersatz.korr ? ' · Repowering ' + fmtEur(land.ersatz.korr) : '')); }
land.aufwertungen.forEach(a => { const $r = $('[data-kind="la"][data-id="' + a.id + '"]'); $r.find('[data-w="faktor"]').text(fmtF(BIL.faf(a.bsf, a.afb))); $r.find('[data-w="wp"]').text(fmtWP(a.wp)); });
$('#box-f1-summe').html('<div class="d-flex flex-wrap gap-3"><span>A1 Biotoptypen <b>' + fmtWP(f1.A1) + '</b></span><span>A2 Funktionen <b>' + fmtWP(f1.A2) + '</b></span><span>A3 Landschaftsbild <b>' + fmtWP(f1.A3) + '</b></span><span class="text-muted">|</span><span>P1 <b>' + fmtWP(f1.P1) + '</b></span><span>P2 <b>' + fmtWP(f1.P2) + '</b></span><span>P3 <b>' + fmtWP(f1.P3) + '</b></span></div>' +
'<div class="d-flex flex-wrap gap-4 mt-1"><span>Kompensationsbedarf Naturhaushalt <b>B1 = ' + fmtWP(f1.B1) + ' WP</b></span><span>Landschaftsbild <b>B2 = ' + fmtWP(f1.B2) + ' WP</b></span>' + (f1.B1 <= 0 && f1.B2 <= 0 && (f1.A1 || f1.A3) ? '<span class="ok">kein Kompensationserfordernis</span>' : '') + '</div>');
$('#badge-eingriff').text(fmtWP(f1.B1));
// Maßnahmen
G.ms.forEach(({ m, c }) => {
const $c = $('.card[data-kind="m"][data-id="' + m.id + '"]');
$c.find('[data-w="bw"]').text(c.bw === null ? '–' : c.bw); $c.find('[data-w="pw"]').text(c.pw === null ? '–' : c.pw);
['S1', 'S2', 'S3', 'S4', 'L1', 'L2'].forEach(k => $c.find('[data-w="' + k + '"]').text(fmtWP(c[k])));
$c.find('[data-w="af"]').text(fmtM2(c.af || 0));
$c.find('[data-w="s1info"]').html((c.bw !== null && c.pw !== null ? 'Δ ' + signed(BIL.r2(c.diff)) + ' WP/m²' : '') + (c.bonus ? ' · ' + esc(c.bonusName) + ' ' + fmtF(c.bonus) : ''));
c.fRows.forEach(k => { const $r = $c.find('[data-kind="mf"][data-id="' + k.id + '"]'); $r.find('[data-w="wp"]').text(fmtWP(k.wp)); $r.find('[data-f="faktor_override"]').attr('placeholder', BIL.faktorZeile(Object.assign({}, k, { faktor_override: '' })) ?? ''); });
$c.find('[data-w="warn"]').html(c.warn.map(w => '<i class="bi bi-exclamation-triangle"></i> ' + esc(w)).join('<br>'));
});
renderBilanz(G);
if ($('#tab-form').hasClass('active')) renderForm(G);
}
/* --------------------------------------------------------- Bilanz */
function renderBilanz(G) {
const ok1 = G.bil1 <= 0.005, ok2 = G.bil2 <= 0.005;
const kachel = (t, v, cls, sub) => '<div class="col-6 col-xl-4"><div class="card h-100"><div class="card-body p-2"><div class="text-muted small">' + t + '</div><div class="wp-big ' + (cls || '') + '">' + v + '</div>' + (sub ? '<div class="small text-muted">' + sub + '</div>' : '') + '</div></div></div>';
let h = '<h6 class="text-uppercase text-muted small mb-2">Gesamtbilanz (' + (G.mast ? 'F14' : 'F13') + ')</h6><div class="row g-2">' +
kachel('Kompensationsbedarf Naturhaushalt B1', fmtWP(G.B1) + ' WP', '', 'aus F1: (A1 + A2) − (P1 + P2)') +
kachel('Kompensationsleistung Naturhaushalt L1', fmtWP(G.L1) + ' WP', 'text-success', 'aus F2 – F11') +
kachel('Bilanzwert Naturhaushalt B1 − L1', fmtWP(G.bil1) + ' WP', ok1 ? 'ok' : 'warn', ok1 ? 'Eingriff in den Naturhaushalt vollständig kompensiert' : 'Defizit – weitere Kompensation erforderlich');
if (G.mast) {
h += kachel('Ersatzzahlung Landschaftsbild E1 (F12)', fmtEur(G.E1), '', 'mastenartiger Eingriff ab 50 m');
} else {
h += kachel('Kompensationsbedarf Landschaftsbild B2', fmtWP(G.B2) + ' WP', '', 'aus F1: A3 − P3') +
kachel('Kompensationsleistung Landschaftsbild L2', fmtWP(G.L2) + ' WP', 'text-success', 'landschaftsästhetische Funktion aus F2 – F11') +
kachel('Bilanzwert Landschaftsbild B2 − L2', fmtWP(G.bil2) + ' WP', ok2 ? 'ok' : 'warn', ok2 ? 'Landschaftsbild vollständig kompensiert' : 'Defizit');
}
h += '</div>';
if (P.meta.einfachfall) h += '<div class="alert alert-info small mt-3 py-2">Als Einfachfall (Anlage 1) gekennzeichnet: kein gutachterlicher Darlegungsbedarf, vereinfachte verbale Gegenüberstellung genügt.</div>';
h += '<h6 class="text-uppercase text-muted small mt-3 mb-2">Beiträge der Maßnahmen</h6>';
if (!G.ms.length) h += '<div class="text-muted small">Noch keine Maßnahmen.</div>';
else {
h += '<table class="table table-sm small"><thead><tr><th>#</th><th>Maßnahme</th><th>Formblatt</th><th class="text-end">Fläche m²</th><th class="text-end">Δ WP/m²</th><th class="text-end">Bonus</th><th class="text-end">L1</th><th class="text-end">L2</th></tr></thead><tbody>';
G.ms.forEach(({ m, c }) => { h += '<tr><td>M' + m.id + '</td><td>' + esc(m.name || btLabel(m.bt_ziel)) + (c.warn.length ? ' <i class="bi bi-exclamation-triangle warn" title="' + esc(c.warn.join(' ')) + '"></i>' : '') + '</td><td>' + m.typ + '</td><td class="text-end">' + fmtM2(c.bezugsflaeche) + '</td><td class="text-end">' + (c.bw !== null && c.pw !== null ? signed(BIL.r2(c.diff)) : '–') + '</td><td class="text-end">' + (c.bonus ? fmtF(c.bonus) : '–') + '</td><td class="text-end">' + fmtWP(c.L1) + '</td><td class="text-end">' + fmtWP(c.L2) + '</td></tr>'; });
h += '<tr class="fw-semibold"><td colspan="6">Summe</td><td class="text-end">' + fmtWP(G.L1) + '</td><td class="text-end">' + fmtWP(G.L2) + '</td></tr></tbody></table>';
}
if (G.bil1 > 0) {
h += '<div class="small text-muted">Orientierung: Das Defizit von ' + fmtWP(G.bil1) + ' WP entspricht z. B. ' + fmtM2(G.bil1 / 10) + ' m² Aufwertung um 10 WP/m² (Acker → extensive Frischwiese) oder ' + Math.ceil(G.bil1 / (30 * 21)) + ' Laubbäumen (30 m² × PW 21).</div>';
}
$('#box-bilanz').html(h);
}
/* --------------------------------------------------- Formblätter */
function kopf() {
const m = P.meta;
return '<div class="fb-kopf mb-2"><b>' + esc(m.name || '(Vorhaben ohne Bezeichnung)') + '</b>' + (m.vkz ? ' · ' + esc(m.vkz) : '') + (m.ort ? ' · ' + esc(m.ort) : '') + (m.traeger ? ' · Vorhabenträger: ' + esc(m.traeger) : '') + (m.bearbeiter ? ' · Bearbeiter: ' + esc(m.bearbeiter) : '') + (m.datum ? ' · ' + esc(m.datum) : '') + '</div>';
}
const aasMark = list => list && list.length ? (BIL.aasSumme(list) > 0 ? '+' : BIL.aasSumme(list) < 0 ? '−' : '+/−') : '';
const funkName = key => (FUNK_ALL.find(f => f.key === key) || {}).name || key;
function tblBiotop(rows, wertName, art) {
let h = '<table><thead><tr><th>#</th><th>BTC</th><th>BTB</th><th>AAS</th><th class="num">GBF m²</th><th class="num">' + wertName + ' WP/m²</th><th class="num">BFB WP</th></tr></thead><tbody>';
let sum = 0;
rows.forEach(r => { sum += r.wp; h += '<tr><td>E' + r.id + '</td><td>' + esc(r.btc || '–') + aasMark(r.aas) + '</td><td>' + esc(r.bt ? r.bt.name : '') + (r.bez ? ' <i>(' + esc(r.bez) + ')</i>' : '') + '</td><td>' + esc((r.aas || []).map(a => a.key).join(', ')) + '</td><td class="num">' + fmtM2(r.gbf) + '</td><td class="num">' + (r.wert === null ? '–' : r.wert) + '</td><td class="num">' + fmtWP(r.wp) + '</td></tr>'; });
h += '<tr class="sum"><td colspan="6">Summe ' + art + '</td><td class="num">' + fmtWP(sum) + '</td></tr></tbody></table>';
return h;
}
function tblFunk(rows, faktorName, sumName, filterFn) {
const rr = rows.filter(filterFn);
let h = '<table><thead><tr><th>Betroffene Funktion</th><th>EWB</th><th class="num">GBF m²</th><th>BSF</th><th>AFB</th><th class="num">' + faktorName + '</th><th class="num">BFF WP</th></tr></thead><tbody>';
let sum = 0;
if (!rr.length) h += '<tr><td colspan="7" class="text-muted">–</td></tr>';
rr.forEach(k => { sum += k.wp; h += '<tr><td>' + esc(funkName(k.funktion)) + (k.bez || k.bemerkung ? ' <i>(' + esc(k.bez || k.bemerkung) + ')</i>' : '') + '</td><td>' + esc(k.ewb) + '</td><td class="num">' + fmtM2(k.gbf) + '</td><td>' + esc(k.bsf) + '</td><td>' + esc(k.afb) + '</td><td class="num">' + fmtF(k.faktor) + '</td><td class="num">' + fmtWP(k.wp) + '</td></tr>'; });
h += '<tr class="sum"><td colspan="6">' + sumName + '</td><td class="num">' + fmtWP(sum) + '</td></tr></tbody></table>';
return h;
}
function renderForm(G) {
G = G || BIL.calcGesamt(P);
const f1 = G.f1;
let h = '<h5>F1 Ermittlung des Kompensationsbedarfs</h5>' + kopf();
h += '<div class="fw-semibold">Bewertung Ausgangszustand – Naturhaushalt, allgemeine Lebensraumfunktion (Biotoptypen)</div>' + tblBiotop(f1.bestand, 'BW', 'A1');
h += '<div class="fw-semibold">Naturhaushalt – Werte und Funktionen besonderer Bedeutung</div>' + tblFunk(f1.fkRows, 'FMF', 'Summe A2', k => k.seite === 'A');
h += '<div class="fw-semibold">Landschaftsbild – landschaftsästhetische Funktion</div>';
if (f1.land.zonen.length) {
h += '<table><thead><tr><th>Wirkzone</th><th class="num">FWZ m²</th><th class="num">WPK WP/m²</th><th>BSL</th><th class="num">AFE %</th><th class="num">AFE m²</th><th>AFB</th><th class="num">FMF</th><th class="num">BFF WP</th></tr></thead><tbody>';
f1.land.zonen.forEach(z => z.stufen.forEach((s, i) => { h += '<tr>' + (i === 0 ? '<td rowspan="3">' + esc(z.name) + '</td><td rowspan="3" class="num">' + fmtM2(z.fwz) + '</td><td rowspan="3" class="num">' + fmtF(z.wpk) + '</td>' : '') + '<td>' + s.stufe + '</td><td class="num">' + s.pct + '</td><td class="num">' + fmtM2(s.afe) + '</td><td>FS</td><td class="num">' + fmtF(s.fmf) + '</td><td class="num">' + fmtWP(s.wp) + '</td></tr>'; }));
h += '<tr class="sum"><td colspan="8">Summe A3</td><td class="num">' + fmtWP(f1.A3) + '</td></tr></tbody></table>';
} else h += '<table><tbody><tr><td class="text-muted">' + (f1.land.typ.key === 'mast_ab50' ? 'mastenartiger Eingriff ab 50 m – Ersatzzahlung nach F12' : 'keine erhebliche Beeinträchtigung') + '</td></tr><tr class="sum"><td>Summe A3: ' + fmtWP(f1.A3) + '</td></tr></tbody></table>';
h += '<div class="fw-semibold">Bewertung Planungszustand – Naturhaushalt, allgemeine Lebensraumfunktion (Biotoptypen)</div>' + tblBiotop(f1.planung, 'PW', 'P1');
h += '<div class="fw-semibold">Naturhaushalt – Werte und Funktionen besonderer Bedeutung (Aufwertung)</div>' + tblFunk(f1.fkRows, 'FAF', 'Summe P2', k => k.seite === 'P');
h += '<div class="fw-semibold">Landschaftsbild – landschaftsästhetische Funktion (Aufwertung)</div>' + tblFunk(f1.land.aufwertungen.map(a => Object.assign({ funktion: 'landschaft', ewb: 'UE' }, a)), 'FAF', 'Summe P3', () => true);
h += '<table><tbody><tr class="sum"><td>([Summe A1 + A2] – [Summe P1 + P2]) Kompensationsbedarf Naturhaushalt (B1)</td><td class="num">' + fmtWP(f1.B1) + '</td></tr><tr class="sum"><td>(Summe A3 – Summe P3) Kompensationsbedarf landschaftsästhetische Funktion (B2)</td><td class="num">' + fmtWP(f1.B2) + '</td></tr></tbody></table><div class="small text-muted">Bei einem Wert ≤ 0 besteht kein Kompensationserfordernis.</div>';
G.ms.forEach(({ m, c }) => {
const ti = typInfo(m.typ); const p = m.params || {};
h += '<h5>' + m.typ + ' ' + esc(ti.name) + ' – M' + m.id + (m.name ? ' „' + esc(m.name) + '“' : '') + '</h5>';
if (m.typ === 'F7') {
h += '<table><thead><tr><th>BKM</th><th>Betroffene Funktion</th><th>EWB</th><th class="num">GBF m²</th><th class="num">BFV</th><th class="num">BFF WP</th></tr></thead><tbody>' +
'<tr><td>M' + m.id + '</td><td>Biotopverbundfunktion</td><td>UE</td><td class="num">' + fmtM2(p.gbf_ue) + '</td><td class="num">1,8</td><td class="num">' + fmtWP(num(p.gbf_ue) * 1.8) + '</td></tr>' +
'<tr><td></td><td>Biotopverbundfunktion (aF = ' + (p.gewaesser ? '(BQ+20)·hS·p' : '(BQ+LLZ)·LKB·p') + ')</td><td>EE</td><td class="num">' + fmtM2(c.af) + '</td><td class="num">1,4</td><td class="num">' + fmtWP(num(c.af) * 1.4) + '</td></tr>' +
'<tr class="sum"><td colspan="5">Summe 1</td><td class="num">' + fmtWP(c.S1) + '</td></tr></tbody></table>';
} else {
const bonusCols = c.bonus ? '<th class="num">' + esc(c.bonusName) + '</th>' : '';
h += '<div class="fw-semibold">Naturhaushalt – Aufwertung allgemeine Lebensraumfunktion (Biotoptypen)</div><table><thead><tr><th>BKM</th><th>Ausgang BTC</th><th>BTB</th><th class="num">BW</th><th>Ziel BTC</th><th>BTB</th><th class="num">PW</th><th class="num">GBF m²</th>' + bonusCols + '<th class="num">BFB WP</th></tr></thead><tbody>' +
'<tr><td>M' + m.id + '</td><td>' + esc(m.bt_ausgang || '–') + aasMark(m.aas_ausgang) + '</td><td>' + esc((byBtc(m.bt_ausgang) || {}).name || '') + '</td><td class="num">' + (c.bw === null ? '–' : c.bw) + '</td><td>' + esc(m.bt_ziel || (c.grm && c.grm.plus_wp ? 'Ausgang +' + c.grm.plus_wp : '–')) + aasMark(m.aas_ziel) + '</td><td>' + esc((byBtc(m.bt_ziel) || {}).name || '') + '</td><td class="num">' + (c.pw === null ? '–' : c.pw) + '</td><td class="num">' + (m.typ === 'F11' ? fmtM2(p.gbf_a) + ' / ' + fmtM2(p.gbf_p) : fmtM2(c.bezugsflaeche)) + '</td>' + (c.bonus ? '<td class="num">' + fmtF(c.bonus) + '</td>' : '') + '<td class="num">' + fmtWP(c.S1) + '</td></tr>' +
'<tr class="sum"><td colspan="' + (c.bonus ? 9 : 8) + '">Summe 1</td><td class="num">' + fmtWP(c.S1) + '</td></tr></tbody></table>';
if (m.typ === 'F5') h += '<div class="small text-muted">Bezug: ' + esc((FK.pik.massnahmen.find(x => x.key === p.pik) || {}).bezug || '') + '</div>';
}
h += '<div class="fw-semibold">Naturhaushalt – Aufwertung der Werte und Funktionen besonderer Bedeutung</div>' + tblFunk(c.fRows, 'FAF', 'Summe 2', k => k.funktion !== 'landschaft' && !BIL.isMinderung(k.afb));
const ab = c.fRows.filter(k => k.funktion !== 'landschaft' && BIL.isMinderung(k.afb));
if (ab.length) h += '<div class="fw-semibold">Naturhaushalt – Abwertung der Werte und Funktionen besonderer Bedeutung</div>' + tblFunk(c.fRows, 'FMF', 'Summe 3', k => k.funktion !== 'landschaft' && BIL.isMinderung(k.afb));
h += '<div class="fw-semibold">Landschaftsbild – Aufwertung der landschaftsästhetischen Funktion</div>' + tblFunk(c.fRows, 'FAF', 'Summe 4', k => k.funktion === 'landschaft');
h += '<table><tbody><tr class="sum"><td>Kompensationsleistung Naturhaushalt (L1) = Summe 1 + Summe 2 − Summe 3</td><td class="num">' + fmtWP(c.L1) + '</td></tr><tr class="sum"><td>Kompensationsleistung landschaftsästhetische Funktion (L2) = Summe 4</td><td class="num">' + fmtWP(c.L2) + '</td></tr></tbody></table>';
if (c.warn.length) h += '<div class="small warn">' + c.warn.map(esc).join('<br>') + '</div>';
});
if (G.mast && f1.land.ersatz) {
const e = f1.land.ersatz;
h += '<h5>F12 Ermittlung der Ersatzzahlung bei mastenartigen Eingriffen über 50 m Höhe</h5><table><thead><tr><th>Bewertungsstufe Landschaftsbild</th><th class="num">ABL %</th><th class="num">BKG €</th><th class="num">FKS % von BKG</th><th class="num">FKS €</th><th class="num">ESZ €</th></tr></thead><tbody>';
e.rows.forEach(r => { h += '<tr><td>' + r.stufe + '</td><td class="num">' + r.abl + '</td><td class="num">' + fmtEur(P.eingriff.landschaft.baukosten) + '</td><td class="num">' + r.proz + '</td><td class="num">' + fmtEur(r.fks) + '</td><td class="num">' + fmtEur(r.esz) + '</td></tr>'; });
h += '<tr class="sum"><td colspan="5">Summe</td><td class="num">' + fmtEur(e.summe) + '</td></tr><tr><td colspan="5">' + esc(e.rep.name) + '</td><td class="num">' + fmtEur(e.korr) + '</td></tr><tr class="sum"><td colspan="5">Ersatzzahlung gesamt (E1)</td><td class="num">' + fmtEur(e.E1) + '</td></tr></tbody></table>';
}
h += '<h5>' + (G.mast ? 'F14 Gesamtbilanz mastenartige Eingriffe > 50 m' : 'F13 Gesamtbilanz') + '</h5><table><thead><tr><th>Bewertungskomponente</th><th class="num">WP</th></tr></thead><tbody>' +
'<tr><td>Kompensationsbedarf Naturhaushalt (B1) aus Formblatt F1</td><td class="num">' + fmtWP(G.B1) + '</td></tr>' +
(G.mast ? '' : '<tr><td>Kompensationsbedarf landschaftsästhetische Funktion (B2) aus Formblatt F1</td><td class="num">' + fmtWP(G.B2) + '</td></tr>') +
'<tr><td>Kompensationsleistung Naturhaushalt (L1) aus den Formblättern F2 bis F11</td><td class="num">' + fmtWP(G.L1) + '</td></tr>' +
(G.mast ? '' : '<tr><td>Kompensationsleistung landschaftsästhetische Funktion (L2) aus den Formblättern F2 bis F11</td><td class="num">' + fmtWP(G.L2) + '</td></tr>') +
'<tr class="sum"><td>(Summe B1 − Summe L1) Bilanzwert Naturhaushalt</td><td class="num ' + (G.bil1 <= 0 ? 'ok' : 'warn') + '">' + fmtWP(G.bil1) + '</td></tr>' +
(G.mast ? '<tr class="sum"><td>Ersatzzahlung gesamt (E1) aus Formblatt F12</td><td class="num">' + fmtEur(G.E1) + '</td></tr>' : '<tr class="sum"><td>(Summe B2 − Summe L2) Bilanzwert landschaftsästhetische Funktion</td><td class="num ' + (G.bil2 <= 0 ? 'ok' : 'warn') + '">' + fmtWP(G.bil2) + '</td></tr>') +
'</tbody></table><div class="small text-muted">Bei einem Wert ≤ 0 ist der Eingriff vollständig kompensiert. Abkürzungen nach Anlage 7: BTC/BTB Biotoptypencode/-bezeichnung, GBF Größe der betroffenen Fläche, BW Biotopwert, PW Planungswert, EWB Einwirkungsbereich, BSF Bewertungsstufe der Funktion, AFB Art der Funktionsbetroffenheit, FMF/FAF Funktionsminderungs-/-aufwertungsfaktor, BFB/BFF Bewertung Fläche nach Biotoptypen/Funktionen.</div>';
$('#box-form').html(h);
}
/* ---------------------------------------------------- Global-UI */
function renderAll() { renderMeta(); renderEF(); renderFK(); renderLand(); renderM(); refreshCalc(); }
$('#btn-neu').on('click', () => { if (!confirm('Alle Eingaben verwerfen und neues Vorhaben beginnen?')) return; KARTE.clear(); P = neuesProjekt(); renderAll(); save(); showTab('tab-vorhaben'); });
$('#btn-export').on('click', () => { download(dateiname('_bilanz.json'), JSON.stringify(snapshot(), null, 1)); });
$('#btn-import').on('click', () => $('#file-import').trigger('click'));
$('#file-import').on('change', function () {
const file = this.files[0]; if (!file) return;
const fr = new FileReader();
fr.onload = () => { try { const o = JSON.parse(fr.result); if (!o.eingriff) throw new Error('kein Bilanz-Projekt'); loadProjekt(o); save(); toast('Vorhaben geladen.', 'success'); } catch (e) { toast('Datei konnte nicht gelesen werden: ' + e.message, 'danger'); } };
fr.readAsText(file); this.value = '';
});
$('#btn-geojson').on('click', () => {
const gj = KARTE.toGeoJSON(); const G = BIL.calcGesamt(P);
gj.features.forEach(ft => {
const ref = (ft.properties.ref || '').split(':'); const id = ref[1];
if (ref[0] === 'ef') { const b = G.f1.bestand.find(x => x.id == id), p = G.f1.planung.find(x => x.id == id); if (b) Object.assign(ft.properties, { bez: b.bez, bt_bestand: b.btc, bw: b.wert, bt_planung: p.btc, pw: p.wert, gbf: b.gbf, wp_bestand: BIL.r2(b.wp), wp_planung: BIL.r2(p.wp) }); }
if (ref[0] === 'm') { const x = G.ms.find(y => y.m.id == id); if (x) Object.assign(ft.properties, { name: x.m.name, typ: x.m.typ, bt_ausgang: x.m.bt_ausgang, bw: x.c.bw, bt_ziel: x.m.bt_ziel, pw: x.c.pw, gbf: x.c.g, L1: BIL.r2(x.c.L1), L2: BIL.r2(x.c.L2) }); }
if (ref[0] === 'fk') { const k = G.f1.fkRows.find(x => x.id == id); if (k) Object.assign(ft.properties, { funktion: funkName(k.funktion), bsf: k.bsf, afb: k.afb, faktor: k.faktor, gbf: k.gbf, wp: BIL.r2(k.wp) }); }
});
download(dateiname('_flaechen_epsg25833.geojson'), JSON.stringify(gj), 'application/geo+json');
});
$('#btn-print').on('click', () => { renderForm(); showTab('tab-form'); setTimeout(() => window.print(), 300); });
$('#btn-hilfe').on('click', () => bootstrap.Modal.getOrCreateInstance('#modal-hilfe').show());
$('#btn-form-refresh').on('click', () => renderForm());
$('button[data-bs-target="#tab-form"]').on('shown.bs.tab', () => renderForm());
/* ------------------------------------------------------- Start */
$.when($.getJSON('data/biotopwertliste.json'), $.getJSON('data/faktoren.json')).done((a, b) => {
BWL = a[0]; FK = b[0];
BIL.init(BWL, FK);
FUNK_ALL = FK.funktionen.concat([{ key: 'landschaft', name: 'Landschaftsästhetische Funktion', schutzgut: 'Landschaftsbild', ewb: ['UE', 'EE'] }]);
if ($.fn.selectpicker && $.fn.selectpicker.Constructor) $.fn.selectpicker.Constructor.BootstrapVersion = '5';
buildOptions();
$('#sel-mtyp').html(optList(FK.massnahmentypen, 'F2', t => t.key, t => t.key + ' – ' + t.name));
KARTE.init('map', { onDrawEnd, onSelect, onModify, onDeleteGeometry, onCoord: t => $('#map-coord').text(t), onDrawState: on => { $('#btn-draw-cancel').prop('hidden', !on); if (!on && !drawCtx) $('#map-hint').text('Auswählen: Klick auf eine Fläche wählt den Eintrag.'); } });
let stored = null;
try { stored = JSON.parse(localStorage.getItem(LS_KEY) || 'null'); } catch (e) { }
if (stored) loadProjekt(stored); else { P = neuesProjekt(); renderAll(); }
$('#autosave-info').text(stored ? 'Stand aus Zwischenspeicher geladen' : 'Biotopwertliste: ' + BWL.typen.filter(t => t.ebene === 'blatt').length + ' Biotoptypen');
}).fail((x, s, e) => { alert('Daten konnten nicht geladen werden (' + s + '). Die Seite muss über einen Webserver (http://) aufgerufen werden.'); });
})();