| Both sides previous revisionPrevious revisionNext revision | Previous revision |
| handleiding_nieuw:sub_spacematrix [2025/05/19 08:44] – support | handleiding_nieuw:sub_spacematrix [2026/05/25 10:44] (current) – external edit 127.0.0.1 |
|---|
| <html> | <html> |
| <html lang="nl"> | <meta charset="UTF-8"> |
| <head> | <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <script src="https://unpkg.com/pizzip@3.1.5/dist/pizzip.min.js"></script> | <meta name="referrer" content="origin"> |
| <script src="https://unpkg.com/docxtemplater@3.29.1/build/docxtemplater.js"></script> | <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> | <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> |
| <meta charset="UTF-8"> | <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"> |
| <title>Spacematrix Rekenblok + Voorzieningen + Parkeren</title> | <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> |
| | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script> |
| <style> | <style> |
| body { font-family: sans-serif; margin: 20px; } | *, *::before, *::after { box-sizing: border-box; } |
| .tabs { display: flex; gap: 10px; margin-bottom: 15px; } | :root { |
| .tab { padding: 10px 15px; background: #ddd; cursor: pointer; border-radius: 5px 5px 0 0; } | --font: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; |
| .tab.active { background: #f0f0f0; font-weight: bold; } | --teal: #0d9488; --teal-h: #0f766e; --teal-light: #f0fdfa; --teal-dim: #ccfbf1; |
| .tabcontent { display: none; border: 1px solid #ccc; padding: 15px; border-radius: 0 5px 5px 5px; } | --green: #22c55e; --green-h: #16a34a; |
| .tabcontent.active { display: block; } | --red: #dc2626; --red-light: #fef2f2; |
| .inputrow { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; } | --amber: #d97706; |
| .inputrow label { width: 240px; } | --gray-50: #f8fafc; --gray-100: #f1f5f9; --gray-200: #e2e8f0; |
| .inputrow input { width: 80px; } | --gray-300: #cbd5e1; --gray-400: #94a3b8; |
| table { border-collapse: collapse; width: 100%; margin-top: 10px; } | --gray-500: #64748b; --gray-600: #475569; --gray-700: #334155; --gray-900: #0f172a; |
| th, td { border: 1px solid #ccc; padding: 5px; text-align: right; } | --radius: 7px; |
| th { background: #f0f0f0; } | --shadow-sm: 0 1px 2px rgba(0,0,0,.06); |
| td:first-child, th:first-child { text-align: left; } | --shadow: 0 1px 3px rgba(0,0,0,.10), 0 1px 2px rgba(0,0,0,.06); |
| .kaartcontainer { position: relative; max-width: 350px; margin-top: 20px; } | } |
| .kaartcontainer img { width: 100%; display: block; } | html,body { margin:0; padding:0; font-family:var(--font); font-size:14px; color:var(--gray-700); background:var(--gray-50); } |
| .stipje { position: absolute; width: 14px; height: 14px; background: red; border-radius: 50%; transform: translate(-50%, -50%); border: 2px solid white; } | |
| </style> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| </head> | |
| <body> | |
| |
| <h1>Spacematrix & Voorzieningen</h1> | /* Anti-copy */ |
| <div class="tabs"> | .sm-page { max-width:1100px; margin:0 auto; padding:16px 16px 64px; user-select:none; -webkit-user-select:none; } |
| <div class="tab active" onclick="switchTab('rekenblok')">Spacematrix Rekenblok</div> | .sm-page input, .sm-page select, .sm-page textarea { user-select:text; -webkit-user-select:text; } |
| <div class="tab" onclick="switchTab('voorzieningen')">Voorzieningen</div> | |
| <div class="tab" onclick="switchTab('parkeren')">Parkeren</div> | |
| <div class="tab" onclick="switchTab('openbareruimte')">Openbare Ruimte</div> | |
| </div> | |
| |
| <div id="rekenblok" class="tabcontent active"> | /* ===== Header ===== */ |
| <div class="bovenblok" style="display: flex; gap: 20px; align-items: flex-start;"> | .sm-header { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; flex-wrap:wrap; margin-bottom:14px; } |
| <div class="inputvelden" style="flex: 1;"> | .sm-header h1 { margin:0; font-size:20px; font-weight:700; line-height:1.1; color:var(--gray-900); } |
| <div class="inputrow"> | .sm-header h1 .v { font-size:11px; font-weight:500; color:var(--gray-400); margin-left:4px; vertical-align:middle; background:var(--gray-100); padding:1px 5px; border-radius:4px; } |
| <label for="opp">Oppervlak projectgebied (m²):</label> | |
| <input id="opp" type="number" value="10000" oninput="updateLaadvermogen()"> | |
| </div> | |
| <div class="inputrow"> | |
| <label for="fsi">FSI:</label> | |
| <input id="fsi" type="number" step="0.01" value="1.6" oninput="updateLaadvermogen()"> | |
| </div> | |
| <div class="inputrow"> | |
| <label for="gsi">GSI:</label> | |
| <input id="gsi" type="number" step="0.01" value="0.3" oninput="updateLaadvermogen()"> | |
| </div> | |
| <div class="inputrow"> | |
| <label>Voorzieningenruimte per woning (m²):</label> | |
| <span id="voorzieningenruimte_display">-</span> | |
| <input type="hidden" id="voorzieningenruimte_waarde" value="0"> | |
| </div> | |
| </div> | |
| <div class="kaartcontainer" style="max-width: 500px;"> | |
| <img src="https://www.sumsonite.nl/wiki/lib/exe/fetch.php?media=handleiding_nieuw:spacematrix:SPACEMATE1.png" alt="Spacematrix kaart"> | |
| <div class="stipje" id="stipje"></div> | |
| </div> | |
| </div> | |
| |
| <div class="onderblok"> | /* ===== Scenario bar ===== */ |
| <table> | .scenario-bar { display:flex; align-items:center; gap:8px; flex-wrap:wrap; justify-content:flex-end; } |
| <thead> | .scenario-bar select { height:32px; padding:0 10px; border-radius:6px; border:1px solid var(--gray-300); background:#fff; font-size:13px; min-width:200px; } |
| <tr><th>Type</th><th>%</th><th>m²/won</th><th>Aantal</th><th>Tot. BVO</th></tr> | .scenario-bar .btn { height:32px; padding:0 12px; border-radius:6px; border:1px solid var(--gray-200); background:#fff; color:var(--gray-700); font-weight:600; cursor:pointer; font-size:12px; font-family:var(--font); transition:background .12s; white-space:nowrap; } |
| </thead> | .scenario-bar .btn:hover { background:var(--gray-100); border-color:var(--gray-300); } |
| <tbody id="woningtypes"></tbody> | .scenario-bar .btn.primary { background:var(--teal); border-color:var(--teal-h); color:#fff; } |
| <tfoot> | .scenario-bar .btn.primary:hover { background:var(--teal-h); } |
| <tr><th>Totaal</th><th id="totaal_perc">-</th><th id="totaal_gewbvo">-</th><th id="totaal_won">-</th><th id="totaal_bvo">-</th></tr> | .scenario-bar .btn.danger { background:var(--red-light); border-color:#fca5a5; color:var(--red); } |
| </tfoot> | .scenario-bar .btn.danger:hover { background:#fee2e2; } |
| </table> | #sm_loaded_label { font-size:12px; color:var(--teal-h); font-weight:500; } |
| | #sm_save_status { font-size:11px; color:var(--gray-500); white-space:nowrap; } |
| |
| <div style="margin-top: 10px; font-size: 0.9em; color: #555;"> | /* ===== Buttons ===== */ |
| <strong>Let op:</strong> de voorzieningenruimte zoals weergegeven in deze samenvatting kan iets afwijken van de berekening op het tabblad <em>Voorzieningen</em>. | button { border:1px solid var(--gray-200); background:#fff; padding:7px 12px; border-radius:var(--radius); cursor:pointer; font-size:13px; font-family:var(--font); font-weight:500; color:var(--gray-700); transition:background .12s,border-color .12s; } |
| Dit komt doordat het aantal woningen iteratief wordt bepaald op basis van de beschikbare ruimte, inclusief ruimte voor voorzieningen en parkeren. | button:hover { background:var(--gray-100); border-color:var(--gray-300); } |
| Bij het toepassen van voorzieningen wordt eerst een schatting gemaakt op basis van een voorlopig aantal woningen. Daarna wordt het aantal woningen opnieuw berekend met de benodigde ruimte per woning. | button.btn-primary { background:var(--teal); border-color:var(--teal-h); color:#fff; font-weight:600; } |
| Hierdoor ontstaat een klein verschil tussen het oorspronkelijk berekende totaal en de daadwerkelijk ingepaste voorzieningenruimte. | button.btn-primary:hover { background:var(--teal-h); } |
| </div> | button.btn-danger { background:var(--red-light); border-color:#fca5a5; color:var(--red); } |
| | button.btn-danger:hover { background:#fee2e2; } |
| |
| <div id="output"></div> | /* ===== Tabs ===== */ |
| <div id="summary"> | .sm-tabs { display:flex; gap:4px; flex-wrap:wrap; border-bottom:2px solid var(--gray-200); margin-bottom:0; } |
| <h3>Samenvatting</h3> | .sm-tab { padding:8px 15px; background:transparent; border:1px solid transparent; border-bottom:none; border-radius:6px 6px 0 0; font-weight:600; font-size:13px; cursor:pointer; color:var(--gray-500); margin-bottom:-2px; transition:background .12s; } |
| <table> | .sm-tab:hover { background:var(--gray-50); color:var(--gray-900); } |
| <tr><td><strong>Totaal laadvermogen (BVO)</strong></td><td id="summary_bvo_tot">-</td></tr> | .sm-tab.active { background:#fff; color:var(--teal); border-color:var(--gray-200); border-bottom-color:#fff; position:relative; z-index:1; } |
| <tr><td>Woonruimte (BVO)</td><td id="summary_wonen_bvo">-</td></tr> | .sm-tabcontent { display:none; border:1px solid var(--gray-200); border-top:none; background:#fff; padding:18px; border-radius:0 0 var(--radius) var(--radius); } |
| <tr><td>Voorzieningenruimte (BVO)</td><td id="summary_voorz_tot">-</td></tr> | .sm-tabcontent.active { display:block; } |
| <tr><td>Parkeerruimte bovengronds (BVO)</td><td id="summary_parkeren_tot">-</td></tr> | |
| | |
| <tr> | |
| <td colspan="2" style="background: #e0e0e0; font-weight: bold; text-transform: uppercase; letter-spacing: 0.5px; font-size: 0.9rem; border-top: 2px solid #999; border-bottom: 1px solid #ccc; color: #333;"> | |
| Kengetallen | |
| </td> | |
| </tr> | |
| | |
| <tr><td>Voorzieningenruimte per woning (m²)</td><td id="summary_voorz_per_won">-</td></tr> | |
| <tr><td>Parkeerruimte per woning (m²)</td><td id="summary_parkeren_per_won">-</td></tr> | |
| <tr><td>Totaal aantal parkeerplaatsen</td><td id="summary_pp_tot">-</td></tr> | |
| <tr><td>Totale woonruimte incl. voorzieningen en parkeren</td><td id="summary_wonen">-</td></tr> | |
| <tr><td>Totaal aantal woningen</td><td id="summary_woningen">-</td></tr> | |
| </table> | |
| </div> | |
| </div> | |
| | |
| <div style="margin-top: 20px;"> | |
| <button onclick="downloadKickstartDump()" style="padding: 10px 20px; font-size: 1rem; background-color: #0074d9; color: black; border: none; border-radius: 5px; cursor: pointer;"> | |
| 📄 Kickstart Dump downloaden | |
| </button> | |
| </div> | |
| | |
| </div> | |
| |
| <div id="voorzieningen" class="tabcontent"> | /* Voorzieningen naast elkaar */ |
| <div class="inputrow"> | .voorz-grid { display:grid; grid-template-columns:1fr 1fr; gap:16px; } |
| <label>Aantal bewoners per woning:</label> | @media(max-width:700px) { .voorz-grid { grid-template-columns:1fr; } } |
| <input id="bewoners_per_woning" type="number" step="0.1" value="2.2"> | |
| </div> | |
| <table> | |
| <thead> | |
| <tr><th>Voorziening</th><th>Norm (m² per 1000)</th><th>Selecteer</th></tr> | |
| </thead> | |
| <tbody id="voorzieningentabel"></tbody> | |
| </table> | |
| <div class="inputrow"> | |
| <button id="btn_voorzieningen" onclick="berekenVoorzieningenruimte()">Toepassen in rekenblok</button> | |
| <div id="voorziening_resultaat" style="margin-left: 10px;"></div> | |
| </div> | |
| <h3>Overzicht geselecteerde voorzieningen</h3> | |
| <table id="voorzieningen_totaal"> | |
| <thead> | |
| <tr><th>Voorziening</th><th>Berekend m²</th></tr> | |
| </thead> | |
| <tbody id="voorziening_summary_tabel"></tbody> | |
| <tfoot> | |
| <tr><th>Totaal</th><th id="voorziening_summary_totaal">-</th></tr> | |
| </tfoot> | |
| </table> | |
| |
| | /* Modal */ |
| | .sm-modal-bg { display:none; position:fixed; inset:0; background:rgba(0,0,0,.5); z-index:3000; align-items:center; justify-content:center; } |
| | .sm-modal-box { background:#ffffff; border-radius:12px; box-shadow:0 8px 40px rgba(0,0,0,.22); display:flex; flex-direction:column; overflow:hidden; } |
| |
| <div style="margin-top: 10px; font-size: 0.9em; color: #555;"> | /* Kaart tab */ |
| <strong>Toelichting:</strong> het weergegeven totaal van de voorzieningenruimte is gebaseerd op een schatting op basis van het huidige aantal woningen. | #kaart-map { height:480px; border-radius:var(--radius); border:1px solid var(--gray-200); } |
| Nadat dit oppervlak per woning is berekend, wordt het aantal woningen in het rekenblok opnieuw doorgerekend. Hierdoor kan het werkelijke totaal afwijken. | .kaart-info { background:var(--teal-light); border:1px solid var(--teal-dim); border-radius:var(--radius); padding:10px 14px; font-size:13px; margin-top:10px; } |
| Deze iteratieve aanpak is nodig omdat de benodigde ruimte per woning invloed heeft op hoeveel woningen uiteindelijk in het plan passen. | .kaart-info strong { color:var(--teal-h); } |
| </div> | |
| </div> | |
| |
| <div id="parkeren" class="tabcontent"> | /* ===== STICKY KEY METRICS BAR ===== */ |
| <h3>Parkeren</h3> | .sm-metrics-bar { |
| <div class="inputrow"> | display:grid; |
| <label>Parkeerplaatsen per woning:</label> | grid-template-columns: 1fr 1px 1fr 1px 1fr 1px 1fr 1px 1fr; |
| <input id="pp_per_woning" type="number" step="0.1" value="1.0"> | background:var(--teal-light); |
| </div> | border:1px solid var(--teal-dim); |
| <div class="inputrow"> | border-top:none; |
| <label>BVO per parkeerplaats (m²):</label> | position:sticky; top:0; z-index:10; |
| <input id="bvo_per_pp" type="number" step="1" value="25"> | |
| </div> | |
| <div class="inputrow"> | |
| <label>% Ondergronds:</label> | |
| <input id="pp_ondergronds" type="number" step="1" value="50"> | |
| </div> | |
| <div class="inputrow"> | |
| <label>% Bovengronds:</label> | |
| <input id="pp_bovengronds" type="number" step="1" value="50"> | |
| </div> | |
| <div class="inputrow"> | |
| <button id="btn_parkeren" onclick="berekenParkeerruimte()">Toepassen in rekenblok</button> | |
| <div id="parkeer_resultaat" style="margin-left: 10px;"></div> | |
| </div> | |
| <h3>Overzicht parkeerplaatsen</h3> | |
| <table> | |
| <tbody> | |
| <tr><td>Aantal parkeerplaatsen totaal</td><td id="pp_totaal">-</td></tr> | |
| <tr><td>Bovengronds (% / aantal)</td><td id="pp_boven">-</td></tr> | |
| <tr><td>Ondergronds (% / aantal)</td><td id="pp_onder">-</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| | |
| <div id="openbareruimte" class="tabcontent"> | |
| <h3>Openbare Ruimte</h3> | |
| <p>Verdeling van de onbebouwde ruimte (oppervlak projectgebied minus bebouwde oppervlak volgens GSI):</p> | |
| <table> | |
| <label>Stedelijkheidstype:</label> | |
| <select id="stedelijkheidstype" onchange="setVerdelingStedelijkheid()"> | |
| <option value="auto">Afhankelijk van FSI/GSI</option> | |
| <option value="centrum">Centrumstedelijk</option> | |
| <option value="gemengd">Gemengd</option> | |
| <option value="suburbaan">Suburbaan</option> | |
| </select> | |
| <thead> | |
| <tr> | |
| <th>Oppervlaktetype</th> | |
| <th>Standaard (%)</th> | |
| <th>Aanpassing (%)</th> | |
| <th>Oppervlak (m²)</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr><td>Groen</td><td>30</td><td><input type="number" id="perc_groen" value="30" oninput="updateOpenbareRuimte()"></td><td id="m2_groen">-</td></tr> | |
| <tr><td>Water</td><td>10</td><td><input type="number" id="perc_water" value="10" oninput="updateOpenbareRuimte()"></td><td id="m2_water">-</td></tr> | |
| <tr><td>Tuinen</td><td>15</td><td><input type="number" id="perc_tuinen" value="15" oninput="updateOpenbareRuimte()"></td><td id="m2_tuinen">-</td></tr> | |
| <tr><td>Rijbanen</td><td>15</td><td><input type="number" id="perc_rijbanen" value="15" oninput="updateOpenbareRuimte()"></td><td id="m2_rijbanen">-</td></tr> | |
| <tr><td>Trottoirs</td><td>10</td><td><input type="number" id="perc_trottoirs" value="10" oninput="updateOpenbareRuimte()"></td><td id="m2_trottoirs">-</td></tr> | |
| <tr><td>Parkeren (bovengronds)</td><td>10</td><td><input type="number" id="perc_parkeren" value="10" oninput="updateOpenbareRuimte()"></td><td id="m2_parkeren">-</td></tr> | |
| <tr><td>Pleinen</td><td>10</td><td><input type="number" id="perc_pleinen" value="10" oninput="updateOpenbareRuimte()"></td><td id="m2_pleinen">-</td></tr> | |
| </tbody> | |
| <tfoot> | |
| <tr><th colspan="3">Totaal</th><th id="m2_totaal_onbebouwd">-</th></tr> | |
| </tfoot> | |
| </table> | |
| <p style="margin-top:10px;"><button onclick="updateOpenbareRuimte()">Herbereken verdeling</button></p> | |
| <div id="warning_openbareruimte" style="color: red; font-weight: bold;"></div> | |
| <canvas id="openbareRuimteChart" width="150" height="150" style="margin-top:20px;"></canvas> | |
| </div> | |
| | |
| <script> | |
| const types = ["egw sociaal", "egw goedkoop", "egw midden", "egw duur", "egw top", "mgw sociaal", "mgw goedkoop", "mgw midden", "mgw duur", "mgw top"]; | |
| const voorzieningen = [ | |
| { naam: "Huisarts", norm: 56 }, | |
| { naam: "Fysiotherapie", norm: 54 }, | |
| { naam: "Tandarts", norm: 57 }, | |
| { naam: "Kinderopvang", norm: 2025 }, | |
| { naam: "Basisschool", norm: 5800 }, | |
| { naam: "Bibliotheek", norm: 60 }, | |
| { naam: "Buurtcentrum", norm: 70 }, | |
| { naam: "Sporthal", norm: 165 }, | |
| { naam: "Zwemmen (overdekt)", norm: 45 }, | |
| { naam: "Winkels dagelijkse goederen", norm: 420 } | |
| ]; | |
| | |
| function switchTab(tabId) { | |
| document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); | |
| document.querySelectorAll('.tabcontent').forEach(c => c.classList.remove('active')); | |
| document.querySelector(`.tab[onclick*="${tabId}"]`).classList.add('active'); | |
| document.getElementById(tabId).classList.add('active'); | |
| } | |
| | |
| function renderWoningtypes() { | |
| const tbody = document.getElementById("woningtypes"); | |
| tbody.innerHTML = ""; | |
| types.forEach((type, i) => { | |
| const row = document.createElement("tr"); | |
| row.innerHTML = ` | |
| <td>${type}</td> | |
| <td><input type="number" id="perc_${i}" value="10" step="0.1" oninput="updateLaadvermogen()"></td> | |
| <td><input type="number" id="bvo_${i}" value="${i < 5 ? 130 : 90}" step="1" oninput="updateLaadvermogen()"></td> | |
| <td id="aantal_${i}">-</td> | |
| <td id="totbvo_${i}">-</td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| } | |
| | |
| function updateStipje(fsi, gsi) { | |
| const stip = document.getElementById("stipje"); | |
| const x = Math.min(100, Math.max(0, gsi * 190)); | |
| const y = Math.max(0, Math.min(100, (3 - fsi) * 35)); | |
| stip.style.left = `${x}%`; | |
| stip.style.top = `${y}%`; | |
| } | |
| | |
| function updateSummary(bvoTot, voorzTot, voorzPerWoning, woonruimte, woningen) { | |
| const pp = parseFloat(document.getElementById("pp_per_woning").value) || 0; | |
| const pp_bvo = parseFloat(document.getElementById("bvo_per_pp").value) || 0; | |
| const pp_bovengronds = parseFloat(document.getElementById("pp_bovengronds").value) || 0; | |
| const parkerenruimtePerWoning = pp * pp_bvo * (pp_bovengronds / 100); | |
| const parkerenTot = woningen * parkerenruimtePerWoning; | |
| | |
| const wonenBVO = bvoTot - voorzTot - parkerenTot; | |
| const parkeerplaatsenTotaal = woningen * pp; | |
| | |
| document.getElementById("summary_bvo_tot").innerText = `${bvoTot.toFixed(0)} m²`; | |
| document.getElementById("summary_wonen_bvo").innerText = `${wonenBVO.toFixed(0)} m²`; | |
| document.getElementById("summary_voorz_tot").innerText = `${voorzTot.toFixed(0)} m²`; | |
| document.getElementById("summary_parkeren_tot").innerText = `${parkerenTot.toFixed(0)} m²`; | |
| | |
| document.getElementById("summary_voorz_per_won").innerText = `${voorzPerWoning.toFixed(1)} m²`; | |
| document.getElementById("summary_parkeren_per_won").innerText = `${parkerenruimtePerWoning.toFixed(1)} m²`; | |
| document.getElementById("summary_pp_tot").innerText = `${parkeerplaatsenTotaal.toFixed(0)}`; | |
| | |
| document.getElementById("summary_wonen").innerText = `${woonruimte.toFixed(0)} m²`; | |
| document.getElementById("summary_woningen").innerText = `${woningen}`; | |
| } | |
| | |
| function updateLaadvermogen() { | |
| const opp = parseFloat(document.getElementById("opp").value); | |
| const fsi = parseFloat(document.getElementById("fsi").value); | |
| const gsi = parseFloat(document.getElementById("gsi").value); | |
| const bvoTot = opp * fsi; | |
| const voorzieningenruimte = parseFloat(document.getElementById("voorzieningenruimte_display").innerText) || 0; | |
| | |
| // Nieuw: parkeerinput ophalen | |
| const pp = parseFloat(document.getElementById("pp_per_woning").value) || 0; | |
| const pp_bvo = parseFloat(document.getElementById("bvo_per_pp").value) || 0; | |
| const pp_bovengronds = parseFloat(document.getElementById("pp_bovengronds").value) || 0; | |
| const parkerenruimte = pp * pp_bvo * (pp_bovengronds / 100); | |
| | |
| updateStipje(fsi, gsi); | |
| | |
| let gewogenBVO = 0; | |
| let totalPerc = 0; | |
| const details = []; | |
| for (let i = 0; i < types.length; i++) { | |
| const perc = parseFloat(document.getElementById(`perc_${i}`).value) || 0; | |
| const bvo = parseFloat(document.getElementById(`bvo_${i}`).value) || 0; | |
| gewogenBVO += (perc / 100) * bvo; | |
| totalPerc += perc; | |
| details.push({ i, perc, bvo }); | |
| } | |
| if (gewogenBVO === 0) return; | |
| | |
| // Nieuwe berekening met voorzieningen én parkeren | |
| let woningen = Math.floor(bvoTot / (gewogenBVO + voorzieningenruimte + parkerenruimte)); | |
| let sumWon = 0; | |
| let sumBVO = 0; | |
| let wonPerType = []; | |
| let restant = woningen; | |
| | |
| for (let i = 0; i < details.length; i++) { | |
| const { perc } = details[i]; | |
| let aant = (perc / 100) * woningen; | |
| let afgerond = Math.floor(aant); | |
| wonPerType.push(afgerond); | |
| restant -= afgerond; | |
| } | |
| const decimaalSort = details.map((d, i) => ({ i, rest: ((d.perc / 100) * woningen) % 1 })) | |
| .sort((a, b) => b.rest - a.rest); | |
| for (let j = 0; j < restant; j++) { | |
| wonPerType[decimaalSort[j].i]++; | |
| } | |
| | |
| for (let i = 0; i < details.length; i++) { | |
| const aant = wonPerType[i]; | |
| const bvoTot = aant * details[i].bvo; | |
| document.getElementById(`aantal_${i}`).innerText = aant; | |
| document.getElementById(`totbvo_${i}`).innerText = bvoTot.toFixed(0); | |
| sumWon += aant; | |
| sumBVO += bvoTot; | |
| } | |
| | |
| document.getElementById("totaal_perc").innerText = `${totalPerc.toFixed(1)}%`; | |
| document.getElementById("totaal_gewbvo").innerText = `${gewogenBVO.toFixed(1)} m²`; | |
| document.getElementById("totaal_won").innerText = sumWon; | |
| document.getElementById("totaal_bvo").innerText = sumBVO.toFixed(0); | |
| | |
| document.getElementById("output").innerHTML = | |
| `<p><b>Gewogen BVO/ehd:</b> ${gewogenBVO.toFixed(1)} m²<br><b>Totaal aantal woningen:</b> ${sumWon}</p>`; | |
| | |
| const voorzieningenTot = sumWon * voorzieningenruimte; | |
| const wonenRuimte = bvoTot - voorzieningenTot - (sumWon * parkerenruimte); | |
| updateSummary(bvoTot, voorzieningenTot, voorzieningenruimte, wonenRuimte, sumWon); | |
| | |
| // Extra update voor parkeren per woning | |
| document.getElementById("summary_parkeren_per_won").innerText = `${parkerenruimte.toFixed(1)} m²`; | |
| } | |
| | |
| function maakVoorzieningentabel() { | |
| const tbody = document.getElementById("voorzieningentabel"); | |
| voorzieningen.forEach((v, i) => { | |
| const row = document.createElement("tr"); | |
| row.innerHTML = ` | |
| <td>${v.naam}</td> | |
| <td>${v.norm}</td> | |
| <td><input type="checkbox" id="vchk_${i}" checked></td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| } | |
| | |
| function berekenVoorzieningenruimte() { | |
| updateLaadvermogen(); // om actuele woningaantal op te halen | |
| | |
| const bewonersPerWoning = parseFloat(document.getElementById("bewoners_per_woning").value); | |
| const woningen = parseFloat(document.getElementById("totaal_won").innerText.replace(/[^0-9]/g, "")) || 0; | |
| const totaalBewoners = woningen * bewonersPerWoning; | |
| | |
| const resultDiv = document.getElementById("voorziening_resultaat"); | |
| const btn = document.getElementById("btn_voorzieningen"); | |
| | |
| if (woningen === 0) { | |
| resultDiv.innerHTML = `<span style="color:red; font-weight:bold;">✖</span> Geen woningen gevonden. Vul eerst FSI/GSI en woningpercentages in.`; | |
| btn.style.border = "1px solid #a00"; | |
| btn.style.background = "#ffe6e6"; | |
| return; | |
| } | |
| | |
| let totaal_m2 = 0; | |
| voorzieningen.forEach((v, i) => { | |
| const checked = document.getElementById(`vchk_${i}`).checked; | |
| if (checked) { | |
| totaal_m2 += v.norm * totaalBewoners / 1000; | |
| } | } |
| }); | .sm-km { padding:10px 12px; text-align:center; min-width:0; } |
| | .sm-km-val { font-size:26px; font-weight:800; color:var(--teal); font-variant-numeric:tabular-nums; line-height:1; } |
| | .sm-km-label { font-size:10px; color:var(--gray-600); text-transform:uppercase; letter-spacing:.04em; margin-top:3px; } |
| | .sm-km-sub { font-size:10px; color:var(--gray-500); margin-top:2px; font-variant-numeric:tabular-nums; } |
| | .sm-km-sep { background:var(--teal-dim); } |
| |
| const m2_per_woning = totaal_m2 / woningen; | /* ===== Form / inputs ===== */ |
| | .sm-inputrow { display:flex; align-items:center; gap:10px; margin-bottom:9px; flex-wrap:nowrap; } |
| document.getElementById("voorzieningenruimte_display").innerText = `${m2_per_woning.toFixed(1)} m²`; | .sm-inputrow label { font-size:13px; color:var(--gray-700); flex-shrink:0; } |
| document.getElementById("voorzieningenruimte_waarde").value = m2_per_woning.toFixed(1); | .sm-inputrow.full-label label { width:280px; } |
| | .sm-inputrow.short-label label { width:50px; } |
| resultDiv.innerHTML = `<span style="color:green; font-weight:bold;">✔</span> ${totaal_m2.toFixed(1)} m² totaal → ${m2_per_woning.toFixed(1)} m² per woning`; | .sm-inputrow input[type="number"], .sm-inputrow select { |
| btn.style.border = "1px solid #0a0"; | width:90px; padding:5px 8px; border:1px solid var(--gray-200); border-radius:var(--radius); font-size:13px; background:#fff; flex-shrink:0; |
| btn.style.background = "#e6ffe6"; | |
| | |
| updateLaadvermogen(); // herberekenen met nieuwe voorzieningenruimte | |
| | |
| // Toon tabel met voorzieningen en m2 | |
| const summaryBody = document.getElementById("voorziening_summary_tabel"); | |
| const summaryTotal = document.getElementById("voorziening_summary_totaal"); | |
| summaryBody.innerHTML = ""; | |
| let subtotal = 0; | |
| | |
| voorzieningen.forEach((v, i) => { | |
| const checked = document.getElementById(`vchk_${i}`).checked; | |
| if (checked) { | |
| const v_m2 = v.norm * totaalBewoners / 1000; | |
| subtotal += v_m2; | |
| const row = document.createElement("tr"); | |
| row.innerHTML = `<td>${v.naam}</td><td>${v_m2.toFixed(1)} m²</td>`; | |
| summaryBody.appendChild(row); | |
| } | } |
| }); | .sm-inputrow input:focus, .sm-inputrow select:focus { outline:none; border-color:var(--teal); box-shadow:0 0 0 2px rgba(13,148,136,.12); } |
| summaryTotal.innerText = `${subtotal.toFixed(1)} m²`; | .sm-display-val { font-size:13px; font-weight:700; color:var(--teal); } |
| | .sm-calc-badge { display:inline-flex; align-items:center; gap:4px; font-size:12px; color:var(--teal-h); font-weight:600; background:var(--teal-light); border:1px solid var(--teal-dim); border-radius:20px; padding:3px 10px; white-space:nowrap; flex-shrink:0; } |
| | .sm-calc-badge::before { content:"="; color:var(--gray-400); font-weight:400; margin-right:2px; } |
| |
| } | /* ===== Cards ===== */ |
| | .sm-box { border:1px solid var(--gray-200); border-radius:var(--radius); background:#fff; padding:13px 15px; margin-bottom:12px; box-shadow:var(--shadow-sm); } |
| | .sm-box-title { font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.05em; color:var(--gray-500); margin-bottom:11px; } |
| | .sm-box-new { border-left:3px solid var(--teal); background:var(--teal-light); } |
| | .sm-box-new .sm-box-title { color:var(--teal-h); } |
| |
| function berekenParkeerruimte() { | /* ===== Spacematrix ===== */ |
| const pp = parseFloat(document.getElementById("pp_per_woning").value) || 0; | .sm-bovenblok { display:flex; gap:20px; align-items:flex-start; flex-wrap:wrap; } |
| const bvo_per_pp = parseFloat(document.getElementById("bvo_per_pp").value) || 0; | .sm-inputvelden { flex:1; min-width:280px; } |
| const bovengronds = parseFloat(document.getElementById("pp_bovengronds").value) || 0; | .sm-kaartcontainer { position:relative; width:420px; max-width:100%; flex-shrink:0; } |
| const woningen = parseFloat(document.getElementById("totaal_won").innerText.replace(/[^0-9]/g, "")) || 0; | .sm-kaartcontainer img { width:100%; display:block; border-radius:var(--radius); border:1px solid var(--gray-200); } |
| | .stipje { position:absolute; width:14px; height:14px; background:#ef4444; border-radius:50%; transform:translate(-50%,-50%); border:2px solid #fff; box-shadow:0 1px 4px rgba(0,0,0,.35); pointer-events:none; } |
| | .sm-legend { margin-top:12px; } |
| | .sm-legend-title { font-size:10px; font-weight:700; color:var(--gray-500); margin-bottom:5px; text-transform:uppercase; letter-spacing:.05em; } |
| | .sm-legend-grid { display:grid; grid-template-columns:1fr 1fr; gap:3px 10px; } |
| | .sm-legend-item { display:flex; align-items:center; gap:5px; font-size:11px; color:var(--gray-700); } |
| | .sm-zone-badge { width:17px; height:17px; border-radius:4px; font-size:10px; font-weight:800; background:var(--gray-100); border:1px solid var(--gray-200); color:var(--gray-600); display:inline-flex; align-items:center; justify-content:center; flex-shrink:0; } |
| | .sm-legend-i { width:13px; height:13px; border-radius:50%; display:inline-flex; align-items:center; justify-content:center; font-size:9px; font-weight:800; border:1px solid var(--gray-200); background:var(--gray-50); color:var(--gray-500); cursor:help; } |
| | .sm-tip { position:relative; display:inline-flex; } |
| | .sm-tip::after { content:attr(data-tip); position:absolute; left:0; top:calc(100% + 7px); width:210px; background:var(--gray-900); color:#fff; padding:6px 9px; border-radius:6px; font-size:11px; line-height:1.35; opacity:0; transform:translateY(-3px); pointer-events:none; transition:opacity .12s,transform .12s; z-index:50; white-space:normal; } |
| | .sm-tip::before { content:""; position:absolute; left:8px; top:calc(100% + 1px); border:5px solid transparent; border-bottom-color:var(--gray-900); opacity:0; transform:translateY(-3px); pointer-events:none; transition:opacity .12s,transform .12s; z-index:51; } |
| | .sm-tip:hover::after, .sm-tip:hover::before { opacity:1; transform:translateY(0); } |
| |
| const ruimte_per_woning = pp * bvo_per_pp * (bovengronds / 100); | /* ===== Sectie-koptekst ===== */ |
| const totaal_ruimte = ruimte_per_woning * woningen; | .sm-section-head { font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.05em; color:var(--gray-600); margin:18px 0 8px; padding-left:10px; border-left:3px solid var(--teal); } |
| |
| // Toon het resultaat en vinkje | /* ===== Tabellen ===== */ |
| const resultDiv = document.getElementById("parkeer_resultaat"); | table { border-collapse:collapse; width:100%; margin-top:8px; } |
| resultDiv.innerHTML = `<span style="color:green; font-weight:bold;">✔</span> ${totaal_ruimte.toFixed(1)} m² totaal → ${ruimte_per_woning.toFixed(1)} m² per woning`; | th,td { border:1px solid var(--gray-200); padding:5px 8px; text-align:right; font-size:13px; } |
| | th { background:var(--gray-50); font-weight:600; color:var(--gray-700); font-size:12px; } |
| | td:first-child, th:first-child { text-align:left; } |
| | tbody tr:nth-child(even) td { background:var(--gray-50); } |
| | td input[type="number"] { width:70px; padding:3px 6px; border:1px solid var(--gray-200); border-radius:4px; font-size:13px; background:#fff; } |
| | td input:focus { outline:none; border-color:var(--teal); } |
| | tfoot td, tfoot th { background:var(--gray-100); font-weight:700; } |
| | .perc-warning { color:var(--red); font-weight:700; } |
| | .sum-section-row td { background:var(--gray-100) !important; font-weight:700; text-transform:uppercase; font-size:10.5px; letter-spacing:.04em; color:var(--gray-500); border-top:2px solid var(--gray-300); padding:4px 8px; } |
| | .sum-totaal td { background:var(--teal-light) !important; font-weight:700; color:var(--teal-h); border-top:2px solid var(--teal-dim); } |
| | .norm-col { width:140px; } .chk-col { width:60px; text-align:center !important; } |
| | .pp-count-cell { color:var(--teal); font-weight:600; font-size:12px; } |
| |
| // Visuele feedback op de knop | /* ===== Export-balk ===== */ |
| const btn = document.getElementById("btn_parkeren"); | .sm-export-bar { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-top:16px; padding-top:14px; border-top:2px solid var(--gray-100); } |
| btn.style.border = "1px solid #0a0"; | |
| btn.style.background = "#e6ffe6"; | |
| |
| // Update berekening | /* ===== Parkeren ===== */ |
| updateLaadvermogen(); | .park-derived-row { display:flex; align-items:center; gap:10px; margin-bottom:9px; } |
| | .park-derived-row label { width:280px; font-size:13px; color:var(--gray-700); } |
| | .sm-derived { font-size:13px; font-weight:600; color:var(--teal); background:var(--teal-light); border:1px solid var(--teal-dim); border-radius:var(--radius); padding:4px 10px; } |
| |
| const totaal_pp = woningen * pp; | /* ===== Onbebouwde ruimte ===== */ |
| const pp_boven_aantal = totaal_pp * (bovengronds / 100); | .or-table input[type="number"] { width:68px; } |
| const ondergronds = parseFloat(document.getElementById("pp_ondergronds").value) || 0; | .sm-tuin-egw { margin-top:12px; padding:12px 14px; border:1px solid var(--gray-200); border-radius:var(--radius); background:var(--gray-50); border-left:3px solid var(--amber); } |
| const pp_onder_aantal = totaal_pp * (ondergronds / 100); | .sm-tuin-egw-title { font-size:12px; font-weight:700; color:var(--amber); margin-bottom:8px; } |
| | .sm-tuin-egw-result{ font-size:12px; margin-top:8px; color:var(--teal); font-weight:600; } |
| |
| document.getElementById("pp_totaal").innerText = `${totaal_pp.toFixed(0)}`; | /* ===== Note ===== */ |
| document.getElementById("pp_boven").innerText = `${bovengronds.toFixed(0)}% / ${pp_boven_aantal.toFixed(0)}`; | .sm-note { font-size:12px; color:var(--gray-600); background:var(--gray-50); border:1px solid var(--gray-200); border-radius:var(--radius); padding:9px 12px; margin-top:10px; line-height:1.5; } |
| document.getElementById("pp_onder").innerText = `${ondergronds.toFixed(0)}% / ${pp_onder_aantal.toFixed(0)}`; | |
| |
| } | /* Meta-badge voor type project */ |
| | .sm-meta-badge { font-size:11px; color:var(--gray-500); font-weight:400; background:var(--gray-100); border:1px solid var(--gray-200); border-radius:4px; padding:1px 5px; margin-left:4px; cursor:help; } |
| |
| function updateOpenbareRuimte() { | /* ===== Collapsibles ===== */ |
| const opp = parseFloat(document.getElementById("opp").value); | .sm-collapsible-header { cursor:pointer; user-select:none; -webkit-user-select:none; } |
| const gsi = parseFloat(document.getElementById("gsi").value); | .sm-collapsible-header:hover .sm-collapse-chevron { color:var(--teal-h); } |
| const bebouwd = opp * gsi; | .sm-collapse-chevron { display:inline-block; color:var(--teal); font-size:10px; width:10px; text-align:center; transition:transform .15s ease; transform:rotate(0deg); margin-right:4px; } |
| const onbebouwd = opp - bebouwd; | .sm-collapsible-header[aria-expanded="true"] .sm-collapse-chevron { transform:rotate(90deg); } |
| | .sm-collapsible .sm-collapsible-body { display:none; } |
| | .sm-collapsible.sm-collapsible-open .sm-collapsible-body { display:block; } |
| | #collapse_egw_tuinen:not(.sm-collapsible-open) .sm-tuin-egw-title { margin-bottom:0; } |
| | .sm-box > .sm-box-title.sm-collapsible-header[aria-expanded="false"] { margin-bottom:0; } |
| | .sm-tab-toolbar { display:flex; justify-content:flex-end; margin-bottom:10px; } |
| | .sm-btn-collapse-all { font-size:11px; padding:4px 12px; color:var(--gray-600); background:transparent; border:1px solid var(--gray-200); border-radius:var(--radius); font-weight:500; cursor:pointer; } |
| | .sm-btn-collapse-all:hover { background:var(--gray-50); color:var(--teal-h); border-color:var(--gray-300); } |
| |
| const types = [ | /* ===== Bronnen-modal ===== */ |
| { id: "groen", label: "Groen" }, | .bt-tab { padding:9px 18px; background:none; border:none; border-bottom:2px solid transparent; font-size:12px; font-weight:600; color:var(--gray-600); cursor:pointer; font-family:var(--font); transition:background .12s,color .12s,border-color .12s; } |
| { id: "water", label: "Water" }, | .bt-tab:hover { background:#fff; color:var(--gray-900); } |
| { id: "tuinen", label: "Tuinen" }, | .bt-tab-active { color:var(--teal); border-bottom-color:var(--teal); background:#fff; } |
| { id: "rijbanen", label: "Rijbanen" }, | |
| { id: "trottoirs", label: "Trottoirs" }, | |
| { id: "parkeren", label: "Parkeren" }, | |
| { id: "pleinen", label: "Pleinen" } | |
| ]; | |
| |
| let totaal = 0; | /* ===== Powered-by badge ===== */ |
| types.forEach(type => { | .sm-powered { position:fixed; left:14px; bottom:14px; z-index:100; display:flex; align-items:center; gap:6px; padding:5px 10px; background:rgba(255,255,255,0.90); border-radius:8px; backdrop-filter:blur(2px); font-size:11px; color:var(--gray-500); text-decoration:none; border:1px solid var(--gray-200); } |
| const perc = parseFloat(document.getElementById(`perc_${type.id}`).value) || 0; | .sm-powered:hover { color:var(--teal); border-color:var(--teal-dim); } |
| totaal += perc; | @media print { .sm-page, .sm-powered { display:none !important; } body::after { content:"Dit document is auteursrechtelijk beschermd — Sumsonite BV"; font-size:24px; padding:40px; } } |
| }); | </style> |
| | </head> |
| | <body oncopy="return false" oncontextmenu="return false" ondragstart="return false" onselectstart="return false" onkeydown="if((event.ctrlKey||event.metaKey)&&['u','s','p'].includes(event.key.toLowerCase())){event.preventDefault();return false;}"> |
| | <div class="sm-page"> |
| |
| if (totaal !== 100) { | <!-- ===== Header + Scenario bar ===== --> |
| document.getElementById("warning_openbareruimte").innerText = `⚠️ Totaal is ${totaal}%. Dit moet 100% zijn.`; | <div class="sm-header"> |
| return; | <h1>Programmaverkenner <span class="v">v2 light</span></h1> |
| } else { | <div class="scenario-bar"> |
| document.getElementById("warning_openbareruimte").innerText = ""; | <span id="sm_loaded_label"></span> |
| } | <select id="sm_scenario_select" onchange="smUI_onScenarioChange()"></select> |
| | <button class="btn primary" onclick="smUI_save()">💾 Opslaan</button> |
| | <button class="btn" onclick="smUI_loadSelected()">📂 Openen</button> |
| | <button class="btn danger" onclick="smUI_deleteSelected()">🗑️</button> |
| | <button class="btn" onclick="smUI_resetAll()">↺ Reset</button> |
| | <button class="btn" onclick="smUI_openHelp()">? Help</button> |
| | <span id="sm_save_status"></span> |
| | </div> |
| | </div> |
| |
| types.forEach(type => { | <!-- ===== Tabs ===== --> |
| const perc = parseFloat(document.getElementById(`perc_${type.id}`).value) || 0; | <div class="sm-tabs"> |
| const m2 = (perc / 100) * onbebouwd; | <button class="sm-tab active" data-tab="locatie" onclick="switchTab('locatie')">Locatie</button> |
| document.getElementById(`m2_${type.id}`).innerText = `${m2.toFixed(0)} m²`; | <button class="sm-tab" data-tab="rekenblok" onclick="switchTab('rekenblok')">Laadvermogen Rekenblok</button> |
| }); | <button class="sm-tab" data-tab="voorzieningen" onclick="switchTab('voorzieningen')">Voorzieningen & Niet-Wonen</button> |
| } | <button class="sm-tab" data-tab="parkeren" onclick="switchTab('parkeren')">Parkeren</button> |
| | <button class="sm-tab" data-tab="openbareruimte" onclick="switchTab('openbareruimte')">Onbebouwde Ruimte</button> |
| | </div> |
| |
| let openbareRuimteChart = null; | <!-- ===== KEY METRICS BAR ===== --> |
| | <div class="sm-metrics-bar"> |
| | <div class="sm-km"> |
| | <div class="sm-km-val" id="km_opp">–</div> |
| | <div class="sm-km-label">m² projectgebied</div> |
| | </div> |
| | <div class="sm-km-sep"></div> |
| | <div class="sm-km"> |
| | <div class="sm-km-val" id="km_fsi">–</div> |
| | <div class="sm-km-label">FSI</div> |
| | </div> |
| | <div class="sm-km-sep"></div> |
| | <div class="sm-km"> |
| | <div class="sm-km-val" id="km_woningen">–</div> |
| | <div class="sm-km-label">woningen</div> |
| | <div class="sm-km-sub" id="km_perc_warning" style="display:none;color:#b00020;font-weight:700;font-size:10px;">⚠️ % ≠ 100</div> |
| | </div> |
| | <div class="sm-km-sep"></div> |
| | <div class="sm-km"> |
| | <div class="sm-km-val" id="km_bvo">–</div> |
| | <div class="sm-km-label">m² laadvermogen BVO</div> |
| | </div> |
| | <div class="sm-km-sep"></div> |
| | <div class="sm-km"> |
| | <div class="sm-km-val" id="km_pp">–</div> |
| | <div class="sm-km-label">parkeerplaatsen totaal</div> |
| | <div class="sm-km-sub" id="km_pp_sub">boven: – · onder: –</div> |
| | </div> |
| | </div> |
| |
| function updateOpenbareRuimte() { | <!-- ===== TAB 0: LOCATIE ===== --> |
| const opp = parseFloat(document.getElementById("opp").value); | <div id="locatie" class="sm-tabcontent active"> |
| const gsi = parseFloat(document.getElementById("gsi").value); | <div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:10px;"> |
| const bebouwd = opp * gsi; | <div> |
| const onbebouwd = opp - bebouwd; | <div style="font-size:15px;font-weight:700;color:var(--gray-900);margin-bottom:2px;">Projectlocatie tekenen</div> |
| | <div style="font-size:12px;color:var(--gray-500);">Teken het projectgebied op de kaart. Oppervlak wordt automatisch berekend en gemeente herkend.</div> |
| | </div> |
| | <div style="display:flex;gap:8px;flex-wrap:wrap;"> |
| | <button onclick="kaartClearPolygon()" style="font-size:12px;padding:5px 10px;">✕ Wis tekening</button> |
| | <button onclick="kaartNaarRekenblok()" class="btn-primary" style="font-size:12px;padding:6px 12px;">✓ Gebruik dit oppervlak →</button> |
| | </div> |
| | </div> |
| | <div style="display:flex;gap:10px;align-items:flex-start;"> |
| | <div style="flex:1;min-width:0;"> |
| | <div id="kaart-map" style="height:560px;border-radius:var(--radius);border:1px solid var(--gray-200);background:var(--gray-100);"></div> |
| | </div> |
| | <div style="width:180px;flex-shrink:0;background:#fff;border:1px solid var(--gray-200);border-radius:var(--radius);padding:10px 0;"> |
| | <div style="font-size:11px;font-weight:600;color:var(--gray-600);padding:0 8px 6px;border-bottom:1px solid var(--gray-100);margin-bottom:4px;">Analysethema's</div> |
| | <div id="kaart-thema-panel"></div> |
| | </div> |
| | </div> |
| | <div class="kaart-info" id="kaart-info" style="display:none;margin-top:8px;"> |
| | <div style="display:flex;gap:24px;flex-wrap:wrap;align-items:center;"> |
| | <div><strong>Oppervlak:</strong> <span id="kaart-opp">–</span> m²</div> |
| | <div><strong>Gemeente:</strong> <span id="kaart-gemeente">–</span></div> |
| | <div><strong>Centroïde:</strong> <span id="kaart-coords" style="font-size:11px;color:var(--gray-500);">–</span></div> |
| | </div> |
| | </div> |
| | <div style="font-size:11px;color:var(--gray-400);margin-top:6px;"> |
| | Gebruik de tekenknop (veelhoek) linksboven op de kaart · Klik "Gebruik dit oppervlak" om door te gaan naar het Laadvermogen Rekenblok |
| | </div> |
| | </div> |
| |
| const types = [ | <!-- ===== TAB 1: LAADVERMOGEN REKENBLOK ===== --> |
| { id: "groen", label: "Groen" }, | <div id="rekenblok" class="sm-tabcontent"> |
| { id: "water", label: "Water" }, | <div class="sm-tab-toolbar"> |
| { id: "tuinen", label: "Tuinen" }, | <button class="sm-btn-collapse-all" id="btn_collapse_rekenblok" onclick="smToggleAllCollapse('rekenblok')">▾ Alles uitklappen</button> |
| { id: "rijbanen", label: "Rijbanen" }, | </div> |
| { id: "trottoirs", label: "Trottoirs" }, | <div class="sm-bovenblok"> |
| { id: "parkeren", label: "Parkeren" }, | <div class="sm-inputvelden"> |
| { id: "pleinen", label: "Pleinen" } | <div class="sm-box"> |
| ]; | <div class="sm-box-title">Plangebied & Spacematrix</div> |
| | <div class="sm-inputrow full-label"> |
| | <label for="opp">Oppervlak projectgebied (m²):</label> |
| | <input id="opp" type="number" value="10000" min="0" oninput="updateLaadvermogen()"> |
| | </div> |
| | <div class="sm-inputrow short-label"> |
| | <label for="fsi">FSI:</label> |
| | <input id="fsi" type="number" step="0.01" value="1.6" min="0" oninput="updateLaadvermogen()"> |
| | <span class="sm-calc-badge" id="laadvermogen_calc">laadvermogen: –</span> |
| | </div> |
| | <div class="sm-inputrow short-label"> |
| | <label for="gsi">GSI:</label> |
| | <input id="gsi" type="number" step="0.01" value="0.3" min="0" max="1" oninput="updateLaadvermogen()"> |
| | <span class="sm-calc-badge" id="footprint_calc">footprint: –</span> |
| | </div> |
| | <div class="sm-inputrow" style="gap:10px;"> |
| | <label for="type_project" style="width:140px;flex-shrink:0;font-size:13px;"> |
| | Type project: |
| | <span class="sm-meta-badge sm-tip" data-tip="Metadata — wordt meegegeven in de seed JSON export.">?</span> |
| | </label> |
| | <select id="type_project" style="width:170px;" onchange="smSaveSessionDebounced()"> |
| | <option value="wonen">Wonen</option> |
| | <option value="wonen_gemengd" selected>Wonen gemengd</option> |
| | <option value="gemengd">Gemengd stedelijk</option> |
| | <option value="werklocatie">Werklocatie</option> |
| | </select> |
| | </div> |
| | <div class="sm-inputrow full-label"> |
| | <label>Voorzieningenruimte per woning:</label> |
| | <span class="sm-display-val" id="voorzieningenruimte_display">–</span> |
| | <input type="hidden" id="voorzieningenruimte_waarde" value="0"> |
| | </div> |
| | </div> |
| | <div class="sm-legend"> |
| | <div class="sm-legend-title">Legenda zones A–H</div> |
| | <div class="sm-legend-grid"> |
| | <div class="sm-legend-item"><span class="sm-zone-badge">A</span><span class="sm-tip" data-tip="Lage bouwhoogte met veel open ruimte."><span class="sm-legend-i">i</span></span> Low-rise spacious</div> |
| | <div class="sm-legend-item"><span class="sm-zone-badge">B</span><span class="sm-tip" data-tip="Lage bouwhoogte met compacte verkaveling."><span class="sm-legend-i">i</span></span> Low-rise compact</div> |
| | <div class="sm-legend-item"><span class="sm-zone-badge">C</span><span class="sm-tip" data-tip="Middelhoge bebouwing met open opzet."><span class="sm-legend-i">i</span></span> Mid-rise open</div> |
| | <div class="sm-legend-item"><span class="sm-zone-badge">D</span><span class="sm-tip" data-tip="Middelhoge bebouwing, ruimtelijk karakter."><span class="sm-legend-i">i</span></span> Mid-rise spacious</div> |
| | <div class="sm-legend-item"><span class="sm-zone-badge">E</span><span class="sm-tip" data-tip="Compact stedelijk, hogere dichtheid."><span class="sm-legend-i">i</span></span> Mid-rise compact</div> |
| | <div class="sm-legend-item"><span class="sm-zone-badge">F</span><span class="sm-tip" data-tip="Gesloten bouwblokken."><span class="sm-legend-i">i</span></span> Mid-rise closed</div> |
| | <div class="sm-legend-item"><span class="sm-zone-badge">G</span><span class="sm-tip" data-tip="Intensieve stedelijke blokken, hoge FSI."><span class="sm-legend-i">i</span></span> Mid-rise super</div> |
| | <div class="sm-legend-item"><span class="sm-zone-badge">H</span><span class="sm-tip" data-tip="Hoogbouw gecombineerd met open maaiveld."><span class="sm-legend-i">i</span></span> High-rise</div> |
| | </div> |
| | </div> |
| | </div> |
| | <div class="sm-kaartcontainer"> |
| | <img src="/wiki/lib/exe/fetch.php?media=handleiding_nieuw:spacematrix:SPACEMATE1.png" |
| | alt="Spacematrix kaart" onerror="this.style.opacity='0.3';"> |
| | <div class="stipje" id="stipje"></div> |
| | </div> |
| | </div> |
| |
| let totaal = 0; | <div class="sm-section-head">Woningprogramma</div> |
| let data = []; | <table> |
| | <thead><tr><th>Type</th><th>%</th><th>m²/won</th><th>Aantal</th><th>Tot. BVO</th></tr></thead> |
| | <tbody id="woningtypes"></tbody> |
| | <tfoot><tr><th>Totaal</th><th id="totaal_perc">–</th><th id="totaal_gewbvo">–</th><th id="totaal_won">–</th><th id="totaal_bvo">–</th></tr></tfoot> |
| | </table> |
| | <div id="output" style="font-size:12px;color:var(--gray-600);margin-top:6px;"></div> |
| |
| types.forEach(type => { | <div class="sm-section-head sm-collapsible-header" |
| const perc = parseFloat(document.getElementById(`perc_${type.id}`).value) || 0; | data-collapse-target="collapse_samenvatting" |
| totaal += perc; | aria-expanded="false" |
| }); | onclick="smToggleCollapse('collapse_samenvatting')"><span class="sm-collapse-chevron">▸</span>Samenvatting laadvermogen</div> |
| | <div id="collapse_samenvatting" class="sm-collapsible"> |
| | <div class="sm-collapsible-body"> |
| | <table> |
| | <tbody> |
| | <tr class="sum-section-row"><td colspan="2">BVO-verdeling</td></tr> |
| | <tr><td>Woonruimte (BVO)</td> <td id="summary_wonen_bvo">–</td></tr> |
| | <tr><td>Voorzieningenruimte wonen (BVO)</td> <td id="summary_voorz_tot">–</td></tr> |
| | <tr><td>Niet-wonen programma (BVO)</td> <td id="summary_nw_totaal">–</td></tr> |
| | <tr><td>Parkeerruimte wonen bovengronds (BVO)</td> <td id="summary_parkeren_wonen_tot">–</td></tr> |
| | <tr><td>Parkeerruimte niet-wonen bovengronds (BVO)</td><td id="summary_parkeren_nw_tot">–</td></tr> |
| | <tr class="sum-totaal"><td><strong>Totaal laadvermogen (BVO)</strong></td><td id="summary_bvo_tot">–</td></tr> |
| | <tr class="sum-section-row"><td colspan="2">Kengetallen</td></tr> |
| | <tr><td>Voorzieningenruimte per woning</td> <td id="summary_voorz_per_won">–</td></tr> |
| | <tr><td>Parkeerruimte per woning bovengronds</td> <td id="summary_parkeren_per_won">–</td></tr> |
| | <tr><td>Totaal parkeerplaatsen</td> <td id="summary_pp_tot">–</td></tr> |
| | <tr><td>BVO ondergronds parkeren</td> <td id="summary_bvo_ondergronds">–</td></tr> |
| | <tr><td>Uitgeefbaar (footprint + tuinen)</td> <td id="summary_uitgeefbaar">–</td></tr> |
| | <tr class="sum-totaal"><td><strong>Totaal woningen</strong></td><td><strong id="summary_woningen">–</strong></td></tr> |
| | </tbody> |
| | </table> |
| | </div> |
| | </div> |
| |
| if (totaal !== 100) { | <div class="sm-export-bar"> |
| document.getElementById("warning_openbareruimte").innerText = `⚠️ Totaal is ${totaal}%. Dit moet 100% zijn.`; | <button onclick="smExportSeedJSON()" style="padding:9px 14px;">⬇ Download seed JSON</button> |
| document.getElementById("m2_totaal_onbebouwd").innerText = "-"; | <button onclick="smUI_importSeed()" style="padding:9px 14px;">⬆ Importeer seed JSON</button> |
| return; | <button onclick="downloadKickstartDump()" style="padding:9px 14px;">📄 Kickstart Dump</button> |
| } else { | </div> |
| document.getElementById("warning_openbareruimte").innerText = ""; | </div> |
| } | |
| |
| // Bereken oppervlak per type en vul tabel | |
| types.forEach(type => { | |
| const perc = parseFloat(document.getElementById(`perc_${type.id}`).value) || 0; | |
| const m2 = (perc / 100) * onbebouwd; | |
| document.getElementById(`m2_${type.id}`).innerText = `${m2.toFixed(0)} m²`; | |
| data.push({ label: type.label, value: m2 }); | |
| }); | |
| |
| // Totaal m² | <!-- ===== TAB 2: VOORZIENINGEN & NIET-WONEN ===== --> |
| const totaal_m2 = data.reduce((sum, d) => sum + d.value, 0); | <div id="voorzieningen" class="sm-tabcontent"> |
| document.getElementById("m2_totaal_onbebouwd").innerText = `${totaal_m2.toFixed(0)} m²`; | <div class="sm-tab-toolbar"> |
| | <button class="sm-btn-collapse-all" id="btn_collapse_voorzieningen" onclick="smToggleAllCollapse('voorzieningen')">▾ Alles uitklappen</button> |
| | </div> |
| | <div class="sm-box"> |
| | <div class="sm-box-title">Instellingen</div> |
| | <div class="sm-inputrow full-label"> |
| | <label for="bewoners_per_woning">Bewoners per woning:</label> |
| | <input id="bewoners_per_woning" type="number" step="0.1" value="2.2" min="0" oninput="updateLaadvermogen()"> |
| | </div> |
| | <div class="sm-inputrow full-label" style="margin-top:8px;"> |
| | <label for="bevolkingsprofiel" style="flex-shrink:0;">CBS bevolkingsprofiel:</label> |
| | <select id="bevolkingsprofiel" style="flex:1;min-width:0;" onchange="smProfielWijzig()"> |
| | <option>Landelijk Nederland 2040</option> |
| | </select> |
| | </div> |
| | <div style="font-size:11px;color:var(--gray-500);margin-top:4px;line-height:1.4;"> |
| | Het profiel bepaalt de doelgroep-verdeling voor voorzieningen die niet per inwoner maar per kinderen, jongeren of ouderen genormeerd zijn. |
| | <span style="color:var(--teal-h);font-weight:500;">In het Sumsonite-abonnement zijn 15+ wijk-specifieke CBS-profielen beschikbaar (o.a. Amsterdam, Rotterdam, Breda, Eindhoven).</span> |
| | </div> |
| | <div class="sm-inputrow" style="align-items:flex-start;gap:8px;margin-top:10px;"> |
| | <input type="checkbox" id="voorz_auto" checked onchange="toggleVoorzAuto();updateLaadvermogen()" style="margin-top:3px;flex-shrink:0;width:auto;"> |
| | <label for="voorz_auto" style="cursor:pointer;width:auto;line-height:1.4;"> |
| | <strong>Automatisch doorrekenen</strong> — berekend uit geselecteerde normen. |
| | Schakel uit voor vaste m²/woning. |
| | </label> |
| | </div> |
| | <div id="voorz_handmatig_wrap" class="sm-inputrow full-label" style="display:none;margin-top:4px;"> |
| | <label for="voorz_handmatig">Voorzieningenruimte per woning (m², handmatig):</label> |
| | <input id="voorz_handmatig" type="number" step="0.1" value="0" min="0" oninput="updateLaadvermogen()"> |
| | </div> |
| | </div> |
| |
| // Vergelijking met onbebouwd oppervlak | <div class="sm-section-head" style="display:flex;justify-content:space-between;align-items:center;"> |
| const verschil = Math.abs(totaal_m2 - onbebouwd); | <div class="sm-collapsible-header" |
| const totaalCell = document.getElementById("m2_totaal_onbebouwd"); | data-collapse-target="collapse_normentabel" |
| if (verschil > 1) { | aria-expanded="false" |
| totaalCell.style.color = "red"; | onclick="smToggleCollapse('collapse_normentabel')" |
| totaalCell.title = `Let op: verschil van ${verschil.toFixed(1)} m² met onbebouwde ruimte`; | style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;flex:1;"> |
| } else { | <span class="sm-collapse-chevron">▸</span> |
| totaalCell.style.color = ""; | <span>Normentabel & selectie</span> |
| totaalCell.title = ""; | <span id="normen_gemeente_label" style="font-size:11px;color:var(--teal);font-weight:500;"></span> |
| } | <span id="normen_peildatum" |
| | onclick="event.stopPropagation();smOpenBronnenModal()" |
| | style="font-size:10px;background:var(--teal-light);color:var(--teal-h);border:1px solid var(--teal-dim);border-radius:12px;padding:2px 10px;cursor:pointer;white-space:nowrap;font-weight:500;transition:background .12s,border-color .12s;" |
| | onmouseenter="this.style.background='var(--teal-dim)';this.style.borderColor='var(--teal)';" |
| | onmouseleave="this.style.background='var(--teal-light)';this.style.borderColor='var(--teal-dim)';" |
| | title="Bekijk landelijke referentienormen"> |
| | 🔄 landelijke referentienormen → |
| | </span> |
| | </div> |
| | <button onclick="smOpenProfielModal()" style="padding:4px 10px;font-size:11px;border:1px solid var(--gray-300);color:var(--gray-600);background:var(--gray-50);border-radius:var(--radius);cursor:pointer;">👥 Bevolkingsprofiel →</button> |
| | </div> |
| | <div id="collapse_normentabel" class="sm-collapsible"> |
| | <div class="sm-collapsible-body"> |
| | <div class="voorz-grid"> |
| | <div> |
| | <table style="width:100%"> |
| | <thead><tr><th>Voorziening <span style="font-weight:400;font-size:10px;color:var(--gray-500);">· doelgroep</span></th><th class="norm-col">Eff. norm<br><span style="font-weight:400;font-size:10px;">/1000 inw.</span></th><th class="chk-col">✓</th></tr></thead> |
| | <tbody id="voorzieningentabel"></tbody> |
| | </table> |
| | </div> |
| | <div> |
| | <div style="font-size:12px;font-weight:600;color:var(--gray-600);margin-bottom:6px;padding-bottom:4px;border-bottom:1px solid var(--gray-200);">Geselecteerde voorzieningen</div> |
| | <table style="width:100%"> |
| | <thead><tr><th>Voorziening</th><th>Berekend m²</th></tr></thead> |
| | <tbody id="voorziening_summary_tabel"></tbody> |
| | <tfoot><tr><th>Totaal</th><th id="voorziening_summary_totaal">–</th></tr></tfoot> |
| | </table> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
| // Chart.js bijwerken | <div class="sm-section-head">Niet-wonen programma</div> |
| const ctx = document.getElementById("openbareRuimteChart").getContext("2d"); | <div class="sm-box sm-box-new"> |
| const labels = data.map(d => d.label); | <p style="font-size:13px;color:var(--gray-700);margin:0 0 10px;"> |
| const values = data.map(d => d.value); | Extra commercieel of werkprogramma per categorie. BVO trekt direct af van laadvermogen. |
| | Parkeernormen → tabblad <strong>Parkeren</strong>. |
| | </p> |
| | <div id="nietwonen_bvo_inputs"></div> |
| | <div style="font-size:12px;margin-top:8px;color:var(--gray-600);"> |
| | Totaal niet-wonen: <strong id="nw_totaal_display">–</strong> |
| | · Bovengronds parkeer-BVO: <strong id="nw_parkbvo_display">–</strong> |
| | </div> |
| | </div> |
| |
| if (openbareRuimteChart) { | <div class="sm-note"> |
| openbareRuimteChart.data.labels = labels; | Voorzieningenruimte/woning en voorzieningenparkeren/woning zijn beide constanten — het systeem is analytisch oplosbaar zonder iteratie. |
| openbareRuimteChart.data.datasets[0].data = values; | </div> |
| openbareRuimteChart.update(); | </div> |
| } else { | |
| openbareRuimteChart = new Chart(ctx, { | |
| type: 'pie', | |
| data: { | |
| labels: labels, | |
| datasets: [{ | |
| data: values, | |
| backgroundColor: [ | |
| '#66bb6a', '#42a5f5', '#ffee58', '#8d6e63', | |
| '#bdbdbd', '#90caf9', '#ff7043' | |
| ] | |
| }] | |
| }, | |
| options: { | |
| plugins: { | |
| legend: { position: 'right' }, | |
| title: { | |
| display: true, | |
| text: 'Verdeling onbebouwde ruimte (m²)' | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| |
| |
| function setVerdelingStedelijkheid() { | <!-- ===== TAB 3: PARKEREN ===== --> |
| const type = document.getElementById("stedelijkheidstype").value; | <div id="parkeren" class="sm-tabcontent"> |
| let waarden = {}; | <div class="sm-tab-toolbar"> |
| | <button class="sm-btn-collapse-all" id="btn_collapse_parkeren" onclick="smToggleAllCollapse('parkeren')">▾ Alles uitklappen</button> |
| | </div> |
| | <div class="sm-box"> |
| | <div class="sm-box-title">Parkeren woningen</div> |
| | <div class="sm-inputrow full-label"> |
| | <label for="pp_per_woning">Parkeerplaatsen per woning (pp/won):</label> |
| | <input id="pp_per_woning" type="number" step="0.1" value="1.0" min="0" oninput="updateLaadvermogen()"> |
| | </div> |
| | <div class="sm-inputrow full-label"> |
| | <label for="bvo_per_pp">BVO per parkeerplaats gebouwd (m²/pp):</label> |
| | <input id="bvo_per_pp" type="number" step="1" value="25" min="10" oninput="updateLaadvermogen()"> |
| | </div> |
| | <div class="sm-inputrow full-label" style="background:var(--gray-50);padding:5px 8px;border-radius:var(--radius);border:1px solid var(--gray-200);"> |
| | <label for="bvo_per_pp_straat" style="color:var(--gray-600);">Oppervlak per pp openbare ruimte (m²/pp):</label> |
| | <input id="bvo_per_pp_straat" type="number" step="0.5" value="12.5" min="5" style="width:80px;" oninput="updateLaadvermogen()"> |
| | </div> |
| | <div class="sm-inputrow full-label"> |
| | <label for="pp_bovengronds">% Bovengronds:</label> |
| | <input id="pp_bovengronds" type="number" step="1" value="50" min="0" max="100" oninput="syncPercOndergronds()"> |
| | </div> |
| | <div class="park-derived-row"> |
| | <label>% Ondergronds (= 100 − bovengronds):</label> |
| | <span class="sm-derived" id="pp_ondergronds_display">50%</span> |
| | </div> |
| | <div class="sm-inputrow full-label" style="background:var(--gray-50);padding:5px 8px;border-radius:var(--radius);margin-top:6px;border:1px solid var(--gray-200);"> |
| | <label for="pp_per_woning_bezoeker" style="color:var(--gray-600);">w.v. bezoekers woningen (pp/won):</label> |
| | <input id="pp_per_woning_bezoeker" type="number" step="0.05" value="0.3" min="0" style="width:70px;" oninput="updateLaadvermogen()"> |
| | <span style="font-size:11px;color:var(--gray-500);">ook te realiseren in openbare ruimte of als gebouwd parkeren</span> |
| | </div> |
| | </div> |
| |
| if (type === "centrum") { | <div class="sm-box sm-box-new"> |
| waarden = { groen: 18, water: 4, tuinen: 8, rijbanen: 25, trottoirs: 20, parkeren: 10, pleinen: 15 }; | <div class="sm-box-title">Parkeernormen niet-wonen & voorzieningen</div> |
| } else if (type === "gemengd") { | <p style="font-size:13px;color:var(--gray-700);margin:0 0 10px;"> |
| waarden = { groen: 20, water: 10, tuinen: 10, rijbanen: 20, trottoirs: 15, parkeren: 15, pleinen: 10 }; | BVO per niet-wonen categorie → tabblad <strong>Voorzieningen & Niet-Wonen</strong>. |
| } else if (type === "suburbaan") { | Voorzieningen-BVO wordt afgeleid uit het woningaantal en de normen. |
| waarden = { groen: 30, water: 15, tuinen: 25, rijbanen: 15, trottoirs: 8, parkeren: 5, pleinen: 2 }; | Zelfde % boven/onder als woningen. |
| } else { | </p> |
| updateOpenbareRuimte(); // Laat FSI/GSI zelf beslissen | <div class="sm-inputrow full-label" style="background:var(--teal-light);padding:6px 8px;border-radius:var(--radius);margin-bottom:8px;"> |
| return; | <label for="pp_voorzieningen" style="font-weight:600;">Voorzieningen (pp / 100 m² BVO):</label> |
| } | <input id="pp_voorzieningen" type="number" step="0.1" value="1.5" min="0" style="width:80px;" oninput="updateLaadvermogen()"> |
| | <span style="font-size:12px;color:var(--teal-h);margin-left:6px;">→ <span id="pp_voorzieningen_count">–</span> pp</span> |
| | </div> |
| | <div id="nietwonen_pp_inputs"></div> |
| | </div> |
| |
| Object.keys(waarden).forEach(id => { | <!-- Dubbelgebruiksanalyse CROW 317 --> |
| document.getElementById(`perc_${id}`).value = waarden[id]; | <div class="sm-box" style="margin-top:12px;"> |
| }); | <div class="sm-box-title sm-collapsible-header" |
| | data-collapse-target="collapse_dubbelgebruik_tabel" |
| | aria-expanded="false" |
| | onclick="smToggleCollapse('collapse_dubbelgebruik_tabel')" |
| | style="display:flex;align-items:center;justify-content:space-between;gap:8px;"> |
| | <span><span class="sm-collapse-chevron">▸</span>Dubbelgebruiksanalyse (CROW 317)</span> |
| | <button onclick="event.stopPropagation();showCrowModal()" style="font-size:11px;padding:3px 9px;border-radius:4px;white-space:nowrap;">ℹ️ Aanwezigheidspercentages</button> |
| | </div> |
| | <div id="collapse_dubbelgebruik_tabel" class="sm-collapsible"> |
| | <div class="sm-collapsible-body"> |
| | <div style="margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid var(--gray-200);"> |
| | <div style="display:flex;gap:20px;flex-wrap:wrap;align-items:center;margin-bottom:8px;"> |
| | <label style="display:flex;align-items:center;gap:7px;cursor:pointer;font-size:13px;font-weight:600;color:var(--teal);"> |
| | <input type="radio" name="dubbelgebruik_route" id="route_crow" value="crow" checked onchange="updateLaadvermogen()"> |
| | Route 1 — CROW aanwezigheidspercentages |
| | </label> |
| | <label style="display:flex;align-items:center;gap:7px;cursor:pointer;font-size:13px;"> |
| | <input type="radio" name="dubbelgebruik_route" id="route_factor" value="factor" onchange="updateLaadvermogen()"> |
| | Route 2 — Eigen factor: |
| | <input id="dubbelgebruik_factor" type="number" step="1" value="10" min="0" max="50" style="width:52px;" oninput="updateLaadvermogen()"> |
| | <span>%</span> |
| | <span style="font-size:11px;color:var(--gray-500);">over totaal norm-based pp</span> |
| | </label> |
| | </div> |
| | <div class="sm-inputrow" style="margin:0;gap:8px;"> |
| | <label style="width:auto;font-size:13px;">Deelmobiliteit:</label> |
| | <input id="deel_reductie_pct" type="number" step="1" value="0" min="0" max="50" style="width:52px;" oninput="updateLaadvermogen()"> |
| | <span style="font-size:13px;">% reductie op woon-pp</span> |
| | <span style="font-size:12px;color:var(--gray-500);">→ 1 deelauto pp per</span> |
| | <input id="deel_ratio" type="number" step="1" value="4" min="1" style="width:48px;" oninput="updateLaadvermogen()"> |
| | <span style="font-size:13px;">gereduceerde pp</span> |
| | </div> |
| | </div> |
| | <div id="dubbelgebruik_tabel_wrap"> |
| | <p style="font-size:12px;color:var(--gray-400);">Vul woningaantal in om de analyse te starten.</p> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
| updateOpenbareRuimte(); | <div class="sm-section-head sm-collapsible-header" |
| } | data-collapse-target="collapse_parkeerbalans" |
| | aria-expanded="false" |
| | onclick="smToggleCollapse('collapse_parkeerbalans')"><span class="sm-collapse-chevron">▸</span>Parkeerbalans per categorie (op basis van norm)</div> |
| | <div id="collapse_parkeerbalans" class="sm-collapsible"> |
| | <div class="sm-collapsible-body"> |
| | <table> |
| | <thead><tr> |
| | <th>Categorie</th> |
| | <th># won / m²BVO</th> |
| | <th>Norm</th> |
| | <th>Aantal pp</th> |
| | <th>% verdeling</th> |
| | </tr></thead> |
| | <tbody id="parkeer_cat_tbody"></tbody> |
| | <tfoot> |
| | <tr class="sum-totaal"> |
| | <th colspan="3">Totaal norm-based (incl. bezoekers)</th> |
| | <th id="pk_tot_pp">–</th> |
| | <th>100%</th> |
| | </tr> |
| | <tr style="background:var(--teal-light);border-top:1px solid var(--teal-dim);"> |
| | <th colspan="3" id="pk_dubbelgebruik_label" style="color:var(--teal-h);font-weight:600;font-size:11px;">Na dubbelgebruik</th> |
| | <th id="pk_benodigd_na_dub" style="color:var(--teal-h);">–</th> |
| | <th>–</th> |
| | </tr> |
| | <tr id="pk_deelmob_row" style="display:none;"> |
| | <th colspan="3" id="pk_deelmob_label">− Deelmobiliteit</th> |
| | <th id="pk_deelmob_pp">–</th> |
| | <th>–</th> |
| | </tr> |
| | <tr style="border-top:2px solid var(--teal-dim);background:var(--teal-light);"> |
| | <th colspan="3"><strong>= Benodigd totaal</strong></th> |
| | <th id="pk_benodigd_pp"><strong>–</strong></th> |
| | <th>–</th> |
| | </tr> |
| | <tr> |
| | <th colspan="3">w.v. Parkeren openbare ruimte</th> |
| | <th id="pp_openbaar_pp">–</th> |
| | <th id="pp_openbaar_bvo">–</th> |
| | </tr> |
| | <tr> |
| | <th colspan="3">w.v. Gebouwd (te bouwen)</th> |
| | <th id="pp_tebouwen_pp">–</th> |
| | <th>–</th> |
| | </tr> |
| | <tr> |
| | <th colspan="2">w.v. Bovengronds</th> |
| | <th id="pp_boven_pct">–</th> |
| | <th id="pp_boven_pp">–</th> |
| | <th id="pp_boven_bvo">–</th> |
| | </tr> |
| | <tr> |
| | <th colspan="2">w.v. Ondergronds</th> |
| | <th id="pp_onder_pct">–</th> |
| | <th id="pp_onder_pp">–</th> |
| | <th id="pp_onder_bvo">–</th> |
| | </tr> |
| | </tfoot> |
| | </table> |
| | </div> |
| | </div> |
| | <span id="pp_totaal" style="display:none"></span> |
| | <span id="pp_totaal_bvo" style="display:none"></span> |
| | <span id="pp_boven_display"style="display:none"></span> |
| | <span id="pp_onder_display"style="display:none"></span> |
| | </div> |
| |
| // 👇 Bouw de volledige woningtype + parkeren-tabel met eerste kolom labels, geschikt voor Kladblok | |
| function buildWoningtypesTableTXT() { | |
| const types = ["egw sociaal", "egw goedkoop", "egw midden", "egw duur", "egw top", | |
| "mgw sociaal", "mgw goedkoop", "mgw midden", "mgw duur", "mgw top"]; | |
| const parkeerkolommen = ["Park. bov.", "Park. ond."]; | |
| const kolommen = [...types, ...parkeerkolommen]; | |
| const colWidth = 13; | |
| const labelWidth = 18; | |
| |
| const pad = (val) => val.toString().padStart(colWidth); | <!-- ===== TAB 4: ONBEBOUWDE RUIMTE ===== --> |
| const head = (val) => val.toString().padEnd(colWidth); | <div id="openbareruimte" class="sm-tabcontent"> |
| const label = (txt) => `| ${txt}`.padEnd(labelWidth); | <div class="sm-tab-toolbar"> |
| | <button class="sm-btn-collapse-all" id="btn_collapse_openbareruimte" onclick="smToggleAllCollapse('openbareruimte')">▾ Alles uitklappen</button> |
| | </div> |
| | <div class="sm-box"> |
| | <div class="sm-box-title">Verdeling onbebouwde ruimte (opp − bebouwde footprint)</div> |
| | <div style="display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-bottom:12px;"> |
| | <div class="sm-inputrow full-label" style="margin:0;"> |
| | <label for="stedelijkheidstype">Stedelijkheidstype:</label> |
| | <select id="stedelijkheidstype" style="width:190px;" onchange="setVerdelingStedelijkheid()"> |
| | <option value="centrum">Centrumstedelijk</option> |
| | <option value="gemengd">Gemengd</option> |
| | <option value="suburbaan">Suburbaan</option> |
| | </select> |
| | </div> |
| | <span id="or_suggestie" style="font-size:11px;color:var(--teal);font-style:italic;"></span> |
| | </div> |
| | <table class="or-table"> |
| | <thead><tr> |
| | <th>Type onbebouwde ruimte</th> |
| | <th style="width:90px;">Preset (%)</th> |
| | <th style="width:90px;">Ingevoerd (%)</th> |
| | <th style="width:110px;">Oppervlak (m²)</th> |
| | </tr></thead> |
| | <tbody> |
| | <tr><td>Openbaar groen</td><td id="preset_groen" style="text-align:right;font-size:12px;color:var(--gray-500);">18</td><td><input type="number" id="perc_groen" value="18" oninput="updateOpenbareRuimte()"></td><td id="m2_groen">–</td></tr> |
| | <tr><td>Water</td><td id="preset_water" style="text-align:right;font-size:12px;color:var(--gray-500);">4</td><td><input type="number" id="perc_water" value="4" oninput="updateOpenbareRuimte()"></td><td id="m2_water">–</td></tr> |
| | <tr><td>Tuinen (privé EGW + MGW-binnenplaatsen)</td><td id="preset_tuinen" style="text-align:right;font-size:12px;color:var(--gray-500);">8</td><td><input type="number" id="perc_tuinen" value="8" oninput="updateOpenbareRuimte()"></td><td id="m2_tuinen">–</td></tr> |
| | <tr><td>Rijbanen</td><td id="preset_rijbanen" style="text-align:right;font-size:12px;color:var(--gray-500);">25</td><td><input type="number" id="perc_rijbanen" value="25" oninput="updateOpenbareRuimte()"></td><td id="m2_rijbanen">–</td></tr> |
| | <tr><td>Trottoirs & fietspad</td><td id="preset_trottoirs" style="text-align:right;font-size:12px;color:var(--gray-500);">20</td><td><input type="number" id="perc_trottoirs" value="20" oninput="updateOpenbareRuimte()"></td><td id="m2_trottoirs">–</td></tr> |
| | <tr><td>Parkeren openbare ruimte</td><td id="preset_parkeren" style="text-align:right;font-size:12px;color:var(--gray-500);">10</td><td><input type="number" id="perc_parkeren" value="10" oninput="updateOpenbareRuimte()"></td><td id="m2_parkeren">–</td></tr> |
| | <tr><td>Pleinen & verblijfsruimte</td><td id="preset_pleinen" style="text-align:right;font-size:12px;color:var(--gray-500);">15</td><td><input type="number" id="perc_pleinen" value="15" oninput="updateOpenbareRuimte()"></td><td id="m2_pleinen">–</td></tr> |
| | </tbody> |
| | <tfoot> |
| | <tr><th colspan="2">Totaal ingevoerd</th> |
| | <th id="or_totaal_perc" style="text-align:right;">–</th> |
| | <th id="m2_totaal_onbebouwd">–</th> |
| | </tr> |
| | </tfoot> |
| | </table> |
| | <div id="warning_openbareruimte" style="color:var(--red);font-weight:700;margin-top:6px;font-size:13px;"></div> |
| | </div> |
| |
| const perc = types.map((_, i) => document.getElementById(`perc_${i}`)?.value || "0"); | <!-- EGW Privétuinen --> |
| const bvo = types.map((_, i) => document.getElementById(`bvo_${i}`)?.value || "0"); | <div class="sm-tuin-egw sm-collapsible" id="collapse_egw_tuinen"> |
| const aantallen = types.map((_, i) => document.getElementById(`aantal_${i}`)?.innerText || "0"); | <div class="sm-tuin-egw-title sm-collapsible-header" |
| const bvo_tot = types.map((_, i) => document.getElementById(`totbvo_${i}`)?.innerText || "0"); | data-collapse-target="collapse_egw_tuinen" |
| | aria-expanded="false" |
| | onclick="smToggleCollapse('collapse_egw_tuinen')"><span class="sm-collapse-chevron">▸</span>EGW Privétuinen |
| | <span style="font-weight:400;font-size:11px;">— verificatie van de Tuinen-rij hierboven</span> |
| | </div> |
| | <div class="sm-collapsible-body"> |
| | <p style="font-size:12px;color:var(--gray-600);margin:0 0 10px;"> |
| | De rij <strong>Tuinen</strong> bevat <em>alle</em> privétuinen — EGW-voor/achtertuinen én MGW-binnenplaatsen. |
| | Vul tuindiepte en beukmaat in om te controleren of het tuinpercentage voldoende is voor de EGW's in het programma. |
| | </p> |
| | <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px 20px;max-width:500px;"> |
| | <div class="sm-inputrow" style="margin:0;flex-wrap:nowrap;"> |
| | <label for="egw_tuindiepte" style="width:160px;font-size:13px;flex-shrink:0;">Tuindiepte EGW (m):</label> |
| | <input id="egw_tuindiepte" type="number" step="0.5" value="0" min="0" style="width:70px;" oninput="updateOpenbareRuimte()"> |
| | </div> |
| | <div class="sm-inputrow" style="margin:0;flex-wrap:nowrap;"> |
| | <label for="egw_beukmaat" style="width:160px;font-size:13px;flex-shrink:0;">Beukmaat EGW (m):</label> |
| | <input id="egw_beukmaat" type="number" step="0.1" value="0" min="0" style="width:70px;" oninput="updateOpenbareRuimte()"> |
| | </div> |
| | </div> |
| | <div id="egw_tuin_resultaat" style="margin-top:10px;font-size:12px;color:var(--gray-600);background:var(--gray-50);border:1px solid var(--gray-200);border-radius:var(--radius);padding:8px 12px;line-height:1.6;display:none;"></div> |
| | <div id="egw_tuin_conflict" style="margin-top:8px;display:none;background:var(--red-light);border:1px solid #fca5a5;border-radius:var(--radius);padding:8px 12px;font-size:12px;color:var(--red);font-weight:600;line-height:1.5;"></div> |
| | <div class="sm-tuin-egw-result" id="m2_tuinen_egw">–</div> |
| | </div> |
| | </div> |
| |
| const pp = parseFloat(document.getElementById("pp_per_woning")?.value || 1); | <canvas id="openbareRuimteChart" style="max-width:480px;max-height:280px;margin-top:20px;display:block;"></canvas> |
| const woningen = aantallen.reduce((acc, val) => acc + parseInt(val || 0), 0); | </div> |
| const bvo_pp = parseFloat(document.getElementById("bvo_per_pp")?.value || 25); | |
| const perc_boven = parseFloat(document.getElementById("pp_bovengronds")?.value || 70); | |
| const perc_onder = parseFloat(document.getElementById("pp_ondergronds")?.value || 30); | |
| const pp_boven = Math.round(woningen * pp * (perc_boven / 100)); | |
| const pp_onder = Math.round(woningen * pp * (perc_onder / 100)); | |
| const bvo_boven = pp_boven * bvo_pp; | |
| const bvo_onder = pp_onder * bvo_pp; | |
| |
| // Voeg parkeerdata toe | </div><!-- .sm-page --> |
| perc.push("-"); perc.push("-"); | |
| bvo.push(bvo_pp.toFixed(0), bvo_pp.toFixed(0)); | |
| aantallen.push(pp_boven.toString(), pp_onder.toString()); | |
| bvo_tot.push(bvo_boven.toFixed(0), bvo_onder.toFixed(0)); | |
| |
| const r = []; | <!-- ===== Bronnen modal (vereenvoudigd — alleen per gemeente) ===== --> |
| r.push(label("Type") + kolommen.map(head).join("|") + "|"); | <div id="bronnen_modal" onclick="if(event.target===this)smCloseBronnenModal()" |
| r.push("".padEnd(labelWidth, " ") + kolommen.map(() => "-".repeat(colWidth)).join("|") + "|"); | style="display:none;position:fixed;inset:0;z-index:3000;background:rgba(0,0,0,.5);align-items:center;justify-content:center;"> |
| r.push(label("% totaal") + perc.map(pad).join("|") + "|"); | <div style="background:#ffffff;border-radius:12px;width:min(640px,96vw);max-height:88vh;display:flex;flex-direction:column;box-shadow:0 8px 40px rgba(0,0,0,.18);"> |
| r.push(label("m² BVO/woning") + bvo.map(pad).join("|") + "|"); | <div style="background:var(--teal);color:#fff;padding:14px 18px;border-radius:12px 12px 0 0;display:flex;justify-content:space-between;align-items:flex-start;flex-shrink:0;"> |
| r.push(label("Aantal woningen") + aantallen.map(pad).join("|") + "|"); | <div> |
| r.push(label("Totaal BVO") + bvo_tot.map(pad).join("|") + "|"); | <div style="font-size:15px;font-weight:700;margin-bottom:3px;">Voorzieningsnormen — landelijke referentiewaarden</div> |
| | <div style="font-size:11px;opacity:.8;">Statische normen · jaarlijks bijgewerkt · CBS, DUO, RIVM open data</div> |
| | </div> |
| | <button onclick="smCloseBronnenModal()" style="background:none;border:none;color:#fff;font-size:20px;cursor:pointer;line-height:1;padding:0 4px;opacity:.8;">✕</button> |
| | </div> |
| | <!-- Upsell banner --> |
| | <div style="padding:10px 18px;background:linear-gradient(135deg,#f0fdfa,#fefce8);border-bottom:1px solid var(--teal-dim);flex-shrink:0;"> |
| | <div style="font-size:12px;color:var(--teal-h);font-weight:600;">🔒 Gemeente-specifieke normen beschikbaar met Sumsonite-abonnement</div> |
| | <div style="font-size:11px;color:var(--gray-600);margin-top:2px;">Automatisch afgestemd op uw projectlocatie · maandelijks bijgewerkt via AI · inclusief trendanalyse</div> |
| | </div> |
| | <span id="bt_gemeente_status" style="display:none;"></span> |
| | <!-- Tabel --> |
| | <div style="overflow-y:auto;flex:1;padding:14px 18px;"> |
| | <div id="bt_loading" style="text-align:center;padding:40px;color:var(--gray-500);font-size:13px;">Data laden...</div> |
| | <div id="bt_content" style="display:none;"> |
| | <table style="width:100%;border-collapse:collapse;font-size:12px;"> |
| | <thead> |
| | <tr style="background:var(--gray-50);"> |
| | <th style="padding:7px 10px;border:1px solid var(--gray-200);text-align:left;font-weight:600;">Voorziening</th> |
| | <th style="padding:7px 10px;border:1px solid var(--gray-200);text-align:right;font-weight:600;">Norm (m²)</th> |
| | <th style="padding:7px 10px;border:1px solid var(--gray-200);text-align:left;font-weight:600;font-size:10px;">Eenheid</th> |
| | <th style="padding:7px 10px;border:1px solid var(--gray-200);text-align:left;font-weight:600;font-size:10px;">Bron</th> |
| | </tr> |
| | </thead> |
| | <tbody id="bt_tabel_body"></tbody> |
| | </table> |
| | <div id="bt_footer" style="margin-top:12px;font-size:10px;color:var(--gray-500);line-height:1.6;"></div> |
| | </div> |
| | </div> |
| | <div style="padding:10px 18px;border-top:1px solid var(--gray-200);display:flex;justify-content:space-between;align-items:center;flex-shrink:0;background:var(--gray-50);border-radius:0 0 12px 12px;"> |
| | <div id="bt_peildatum" style="font-size:10px;color:var(--gray-500);"></div> |
| | <button onclick="smCloseBronnenModal()" style="padding:7px 18px;background:var(--teal);color:#fff;border:none;border-radius:6px;font-size:12px;cursor:pointer;">Sluiten</button> |
| | </div> |
| | </div> |
| | </div> |
| |
| r.push(""); | <!-- ===== CROW 317 Modal ===== --> |
| r.push(`Totale aantal woningen: ${woningen}`); | <div id="crow_modal" onclick="if(event.target===this)closeCrowModal()" |
| r.push(`Gemiddelde BVO per woning: 110.0`); | style="display:none;position:fixed;inset:0;z-index:200;background:rgba(0,0,0,0.5);align-items:center;justify-content:center;"> |
| | <div style="background:#fff;border-radius:10px;max-width:96vw;max-height:92vh;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,0.3);display:flex;flex-direction:column;"> |
| | <div style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #e2e8f0;flex-shrink:0;gap:16px;"> |
| | <strong style="font-size:14px;">CROW Publicatie 317 — Aanwezigheidspercentages parkeren</strong> |
| | <button onclick="closeCrowModal()" style="padding:4px 12px;flex-shrink:0;">✕ Sluiten</button> |
| | </div> |
| | <div style="overflow:auto;padding:12px 16px;"> |
| | <table style="border-collapse:collapse;font-size:12px;white-space:nowrap;"> |
| | <thead><tr> |
| | <th style="text-align:left;padding:6px 10px;background:#f8fafc;border:1px solid #e2e8f0;min-width:210px;">Functie</th> |
| | <th style="padding:5px 8px;background:#f8fafc;border:1px solid #e2e8f0;text-align:center;font-size:10px;">Wkd<br>Ocht.</th> |
| | <th style="padding:5px 8px;background:#f8fafc;border:1px solid #e2e8f0;text-align:center;font-size:10px;">Wkd<br>Mid.</th> |
| | <th style="padding:5px 8px;background:#f8fafc;border:1px solid #e2e8f0;text-align:center;font-size:10px;">Wkd<br>Avond</th> |
| | <th style="padding:5px 8px;background:#f8fafc;border:1px solid #e2e8f0;text-align:center;font-size:10px;">Koop-<br>avond</th> |
| | <th style="padding:5px 8px;background:#f8fafc;border:1px solid #e2e8f0;text-align:center;font-size:10px;">Nacht</th> |
| | <th style="padding:5px 8px;background:#f8fafc;border:1px solid #e2e8f0;text-align:center;font-size:10px;">Zat.<br>Mid.</th> |
| | <th style="padding:5px 8px;background:#f8fafc;border:1px solid #e2e8f0;text-align:center;font-size:10px;">Zat.<br>Avond</th> |
| | <th style="padding:5px 8px;background:#f8fafc;border:1px solid #e2e8f0;text-align:center;font-size:10px;">Zon.<br>Mid.</th> |
| | </tr></thead> |
| | <tbody id="crow_modal_tbody"></tbody> |
| | </table> |
| | <p style="font-size:11px;color:#94a3b8;margin-top:8px;">Bron: Aanwezigheidspercentages op basis van CROW publicatie 317 · Groen = hoog aanwezig · Grijs = afwezig</p> |
| | </div> |
| | </div> |
| | </div> |
| |
| return r.join("\n"); | <!-- ===== Powered-by badge ===== --> |
| } | <a class="sm-powered" href="https://www.sumsonite.nl" target="_blank" rel="noopener" title="Sumsonite"> |
| | ◈ Sumsonite |
| | </a> |
| |
| | <script src="/wiki/tools/dichtheid_V2/sm_rekenmachine_light.js?v=2"></script> |
| | <script src="/wiki/tools/dichtheid_V2/kaart_light.js?v=2"></script> |
| | <script src="/wiki/tools/dichtheid_V2/bronnen_light.js?v=2"></script> |
| |
| function buildVoorzieningenTable() { | <!-- CROW 317 tabel vullen na page load --> |
| const voorzieningen = [ | <script> |
| { naam: "Huisarts" }, { naam: "Fysiotherapie" }, { naam: "Tandarts" }, | document.addEventListener("DOMContentLoaded", function(){ |
| { naam: "Kinderopvang" }, { naam: "Basisschool" }, { naam: "Bibliotheek" }, | var tbody = document.getElementById("crow_modal_tbody"); |
| { naam: "Buurtcentrum" }, { naam: "Sporthal" }, { naam: "Zwemmen (overdekt)" }, | if(!tbody || typeof CROW_317 === "undefined") return; |
| { naam: "Winkels dagelijks goed." } | CROW_317.volledig.forEach(function(row){ |
| ]; | var naam = row[0], pcts = row[1]; |
| let rows = ["Type | Oppervlak (m²)"]; | var tr = document.createElement("tr"); |
| | var html = '<td style="padding:5px 10px;border:1px solid #e2e8f0;">' + naam + '</td>'; |
| voorzieningen.forEach((v, i) => { | pcts.forEach(function(p){ |
| const row = document.getElementById(`vchk_${i}`); | var bg = "", fw = ""; |
| if (row && row.checked) { | if(p >= 90){ bg = "background:#dcfce7;"; fw = "font-weight:700;"; } |
| const el = document.querySelector(`#voorziening_summary_tabel tr:nth-child(${rows.length}) td:last-child`); | else if(p >= 50){ bg = "background:#f0fdfa;"; } |
| const m2 = el ? el.innerText : "-"; | else if(p <= 5){ bg = ""; fw = "color:#d1d5db;"; } |
| rows.push(`${v.naam} | ${m2}`); | html += '<td style="padding:4px 8px;border:1px solid #e2e8f0;text-align:center;' + bg + fw + '">' + p + '%</td>'; |
| } | |
| }); | |
| | |
| return rows.join("\n"); | |
| } | |
| | |
| function buildPlankaartTable() { | |
| const getVal = (id) => parseFloat(document.getElementById(id)?.innerText?.replace(" m²", "") || 0); | |
| const getInput = (id) => parseFloat(document.getElementById(id)?.value || 0); | |
| | |
| const ids = ["opp", "m2_groen", "m2_water", "m2_tuinen", "m2_rijbanen", "m2_trottoirs", "m2_parkeren", "m2_pleinen"]; | |
| const labels = ["Totaal bruto plangebied", "Groen", "Water", "Tuinen", "Rijbanen", "Trottoirs", "Parkeren (bovengr.)", "Pleinen"]; | |
| | |
| const opp = getInput("opp"); | |
| const gsi = getInput("gsi"); | |
| const footprint = gsi * opp; | |
| const tuin = getVal("m2_tuinen"); | |
| const uitgeefbaar = Number(footprint)*1 + Number(tuin)*1; | |
| const verharding = getVal("m2_rijbanen") + getVal("m2_trottoirs") + getVal("m2_parkeren") + getVal("m2_pleinen"); | |
| | |
| const col1 = 30; | |
| const col2 = 16; | |
| const pad = (txt, len) => txt.toString().padEnd(len); | |
| const val = (v) => v.toString().padStart(col2); | |
| | |
| let rows = []; | |
| rows.push("Omschrijving".padEnd(col1) + "| Oppervlak (m²)"); | |
| rows.push("-".repeat(col1) + "|" + "-".repeat(col2)); | |
| | |
| ids.forEach((id, i) => { | |
| const el = document.getElementById(id); | |
| const waarde = el?.value || el?.innerText || "-"; | |
| rows.push(pad(labels[i], col1) + "|" + val(waarde)); | |
| }); | |
| | |
| rows.push("-".repeat(col1) + "|" + "-".repeat(col2)); | |
| rows.push(pad("Footprint (GSI x opp)", col1) + "|" + val(footprint.toFixed(0))); | |
| rows.push(pad("Totaal uitgeefbaar", col1) + "|" + val(uitgeefbaar.toFixed(0))); | |
| rows.push(pad("Rijbanen", col1) + "|" + val(getVal("m2_rijbanen"))); | |
| rows.push(pad("Trottoirs", col1) + "|" + val(getVal("m2_trottoirs"))); | |
| rows.push(pad("Parkeren (bovengr.)", col1) + "|" + val(getVal("m2_parkeren"))); | |
| rows.push(pad("Pleinen", col1) + "|" + val(getVal("m2_pleinen"))); | |
| rows.push("-".repeat(col1) + "|" + "-".repeat(col2)); | |
| rows.push(pad("Totaal verharding", col1) + "|" + val(verharding.toFixed(0))); | |
| rows.push(pad("Groen", col1) + "|" + val(getVal("m2_groen"))); | |
| rows.push(pad("Water", col1) + "|" + val(getVal("m2_water"))); | |
| | |
| return rows.join("\n"); | |
| } | |
| | |
| function buildGrexTable() { | |
| const getVal = id => parseFloat(document.getElementById(id)?.innerText?.replace(' m²', '') || 0); | |
| const rijbanen = getVal("m2_rijbanen"); | |
| const trottoirs = getVal("m2_trottoirs"); | |
| const parkeren = getVal("m2_parkeren"); | |
| const pleinen = getVal("m2_pleinen"); | |
| const groen = getVal("m2_groen"); | |
| const water = getVal("m2_water"); | |
| const totaalopp = parseFloat(document.getElementById("opp")?.value || 0); | |
| const nazorg = rijbanen * 0.10; | |
| | |
| return [ | |
| "Categorie | Omschrijving | Oppervlak (m²)", | |
| `Verwerving | Percelen te verwerven of in te brengen | ${totaalopp.toFixed(0)}`, | |
| `Grondwerk | Graven watergangen | ${water.toFixed(0)}`, | |
| `Bouwrijp maken | Bouwstraten | ${rijbanen.toFixed(0)}`, | |
| `Woonrijp maken | Rijbanen | ${rijbanen.toFixed(0)}`, | |
| `Woonrijp maken | Trottoirs | ${trottoirs.toFixed(0)}`, | |
| `Woonrijp maken | Parkeren | ${parkeren.toFixed(0)}`, | |
| `Woonrijp maken | Pleinen | ${pleinen.toFixed(0)}`, | |
| `Woonrijp maken | Groen | ${groen.toFixed(0)}`, | |
| "Woonrijp maken | Bomen en groenstructuur |", | |
| "Woonrijp maken | Verdere aankleding openbare ruimte |", | |
| `Nazorg | Herstraten bestratingen (10% rijbanen) | ${nazorg.toFixed(0)}`, | |
| "Onvoorzien | Onvoorzien (5-15%) |" | |
| ].join("\n"); | |
| } | |
| | |
| async function downloadKickstartDump() { | |
| console.log("🔍 Start Kickstart TXT-generatie"); | |
| | |
| try { | |
| const response = await fetch('https://www.sumsonite.nl/wiki/lib/exe/fetch.php?media=handleiding_nieuw:spacematrix:kickstart_template_final.txt&v=' + Date.now()); | |
| if (!response.ok) throw new Error(`Bestand niet bereikbaar (${response.status})`); | |
| | |
| let template = await response.text(); | |
| console.log("✅ Template geladen"); | |
| | |
| const bvo_per_pp = parseFloat(document.getElementById("bvo_per_pp").value) || 0; | |
| const pp_bovengronds = parseFloat(document.getElementById("pp_bovengronds").value) || 0; | |
| const pp_ondergronds = parseFloat(document.getElementById("pp_ondergronds").value) || 0; | |
| const woningen = parseFloat(document.getElementById("summary_woningen").innerText) || 0; | |
| const pp = parseFloat(document.getElementById("pp_per_woning").value) || 0; | |
| | |
| const bvo_per_pp_boven = bvo_per_pp.toFixed(1); | |
| const bvo_per_pp_onder = bvo_per_pp.toFixed(1); | |
| | |
| const parkeeropp_totaal = woningen * pp * bvo_per_pp; | |
| const parkeeropp_boven = parkeeropp_totaal * (pp_bovengronds / 100); | |
| const parkeeropp_onder = parkeeropp_totaal * (pp_ondergronds / 100); | |
| | |
| const data = { | |
| datum: new Date().toISOString().slice(0, 10), | |
| aantal_woningen: woningen, | |
| bvo_per_woning: "110.0", | |
| pp_boven: document.getElementById("pp_boven")?.innerText || "–", | |
| pp_onder: document.getElementById("pp_onder")?.innerText || "–", | |
| bvo_boven: document.getElementById("summary_parkeren_tot")?.innerText || "–", | |
| bvo_onder: "–", | |
| bvo_per_pp_boven: bvo_per_pp_boven, | |
| bvo_per_pp_onder: bvo_per_pp_onder, | |
| totbvo_boven: parkeeropp_boven.toFixed(0), | |
| totbvo_onder: parkeeropp_onder.toFixed(0), | |
| woningtypes_table: buildWoningtypesTableTXT(), | |
| voorzieningen_table: buildVoorzieningenTableTXT(), | |
| plankaart_table: buildPlankaartTableTXT(), | |
| grex_table: buildGrexTableTXT() | |
| }; | |
| | |
| for (let i = 0; i < 10; i++) { | |
| data[`perc_${i}`] = document.getElementById(`perc_${i}`)?.value || "0"; | |
| data[`bvo_${i}`] = document.getElementById(`bvo_${i}`)?.value || "0"; | |
| data[`aantal_${i}`] = document.getElementById(`aantal_${i}`)?.innerText || "0"; | |
| data[`totbvo_${i}`] = document.getElementById(`totbvo_${i}`)?.innerText || "0"; | |
| } | |
| | |
| Object.entries(data).forEach(([key, val]) => { | |
| template = template.replaceAll(`{{${key}}}`, val); | |
| }); | }); |
| | tr.innerHTML = html; |
| const blob = new Blob([template], { type: 'text/plain;charset=utf-8' }); | tbody.appendChild(tr); |
| saveAs(blob, 'kickstart_dump.txt'); | |
| alert("✅ Kickstart Dump is succesvol gegenereerd en gedownload"); | |
| } catch (error) { | |
| console.error("❌ Fout bij genereren:", error); | |
| alert("❌ Fout: " + error.message); | |
| } | |
| } | |
| | |
| function buildVoorzieningenTableTXT() { | |
| let output = ""; | |
| const rows = document.querySelectorAll("#voorziening_summary_tabel tr"); | |
| rows.forEach(row => { | |
| const cells = row.querySelectorAll("td"); | |
| if (cells.length === 2) { | |
| const naam = cells[0].innerText.trim(); | |
| const m2 = cells[1].innerText.trim(); | |
| output += `| ${naam.padEnd(25)} | ${m2.padStart(14)} |\n`; | |
| } | |
| }); | }); |
| return output || "| Geen voorzieningen geselecteerd | – |"; | }); |
| } | |
| | |
| function buildPlankaartTableTXT() { | |
| return `Omschrijving | Oppervlak (m²) | |
| ----------------------------|---------------- | |
| Totaal bruto plangebied | ${document.getElementById("opp").value} | |
| ----------------------------|---------------- | |
| Tuinen | ${getVal("m2_tuinen")} | |
| Footprint (GSI x opp) | ${(parseFloat(document.getElementById("gsi").value) * parseFloat(document.getElementById("opp").value)).toFixed(0)} | |
| ----------------------------|---------------- | |
| Totaal uitgeefbaar | ${getVal("m2_tuinen") + (parseFloat(document.getElementById("gsi").value) * parseFloat(document.getElementById("opp").value)).toFixed(0)} | |
| Rijbanen | ${getVal("m2_rijbanen")} | |
| Trottoirs | ${getVal("m2_trottoirs")} | |
| Parkeren (bovengr.) | ${getVal("m2_parkeren")} | |
| Pleinen | ${getVal("m2_pleinen")} | |
| ----------------------------|---------------- | |
| Totaal verharding | ${getVal("m2_rijbanen") + getVal("m2_trottoirs") + getVal("m2_parkeren") + getVal("m2_pleinen")} | |
| Groen | ${getVal("m2_groen")} | |
| Water | ${getVal("m2_water")}`; | |
| } | |
| | |
| function buildGrexTableTXT() { | |
| return `Categorie | Omschrijving | Oppervlak (m²) | |
| --------------------|----------------------------------------|---------------- | |
| Verwerving | Percelen te verwerven of in te brengen | ${document.getElementById("opp").value} | |
| Grondwerk | Graven watergangen | ${getVal("m2_water")} | |
| Bouwrijp maken | Bouwstraten | ${getVal("m2_rijbanen")} | |
| Woonrijp maken | Rijbanen | ${getVal("m2_rijbanen")} | |
| Woonrijp maken | Trottoirs | ${getVal("m2_trottoirs")} | |
| Woonrijp maken | Parkeren | ${getVal("m2_parkeren")} | |
| Woonrijp maken | Pleinen | ${getVal("m2_pleinen")} | |
| Woonrijp maken | Groen | ${getVal("m2_groen")} | |
| Woonrijp maken | Bomen en groenstructuur | | |
| Woonrijp maken | Verdere aankleding openbare ruimte | | |
| Nazorg | Herstraten bestratingen (10% rijbanen) | ${(getVal("m2_rijbanen") * 0.10).toFixed(0)} | |
| Onvoorzien | Onvoorzien (5-15%) | `; | |
| } | |
| | |
| function getVal(id) { | |
| const el = document.getElementById(id); | |
| return el ? parseFloat(el.innerText.replace(" m²", "")) || 0 : 0; | |
| } | |
| | |
| | |
| | |
| window.onload = () => { | |
| renderWoningtypes(); | |
| maakVoorzieningentabel(); | |
| updateLaadvermogen(); | |
| }; | |
| </script> | </script> |
| |
| </body> | |
| </html> | </html> |