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>
852 lines
85 KiB
JavaScript
852 lines
85 KiB
JavaScript
/* 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 & 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.'); });
|
||
})();
|