303.525 datasets geïndexeerd1.419 uitgevers
Open Data IndexDe index van open data over Nederland
Open Data Index›Bouwen›Zonnepanelen-check voor je eigen dak

Zonnepanelen-check voor je eigen dak

Typ een postcode en huisnummer en zie hoe groot je dakvlakken zijn, welke kant ze op kijken, hoeveel zon er valt en wat panelen ruwweg zouden opleveren, met open data van BAG, AHN, KNMI en RVO.

Je AI-assistentOpen Data Index MCP aan
De MCP-server van Open Data Index staat aan in je chat. Zo leest de assistent dit project en de bronnen zelf.
Jij plakt de prompt van dit project
De assistent haalt op bij Open Data Index
  • geef_project("zonnepanelen-check")
  • geef_bron("pdok-bag")
  • geef_bron("ahn")
  • geef_bron("knmi-daggegevens")
De assistent

Ik heb het project, de geteste aanroepen en de valkuilen. Ik bouw een tool met Basisregistratie Adressen en Gebouwen (BAG), Actueel Hoogtebestand Nederland (AHN), KNMI daggegevens van het weer en Potentiekaarten warmte (WarmteAtlas). Zo kan het eruitzien:

Dit bouw je zelf. Kopieer de prompt naar Claude, ChatGPT of Cursor, met de MCP-server aan, of volg de stappen hieronder.

Volg de stappen
Dakzon
Kamperfoeliestraat 1De Bilt
bouwjaar 1987grondvlak 68,1 m²zadeldak ~45°3732VT
nok 9,1 m45°Z
zuid39,4 m² · 28 m² panelen
Opbrengst per jaar≈ 5.600 kWh~5,8 kWp · gemiddeld zonjaar
Buurt De Bilt verbruikt~3.050 kWh
per woning per jaar (RVO, 2014): je wekt 1,8 keer zoveel op

Dakvlakken (AHN4)

  • zuid39,445°
  • noord30,345°
  • plat2,83°
  • west2,354°
  • oost0,331°

Zon in De Bilt, kWh/m²

1.0001.1001.2001.21120162025- - gemiddeld
Prijs van je offerte€

Vul je offerte in voor de terugverdientijd.
Schatting: schaduw en dakkapellen tellen niet mee.

Mockup

Dit is een mockup, geen werkende versie, met de cijfers voor Kamperfoeliestraat 1 in De Bilt uit onze proefdraai van 16 oktober 2026 en de KNMI-straling 2016–2025, opgehaald op 17 oktober 2026. De MCP-server zet je aan met het adres op Voor AI.

Voor wie

Huiseigenaren die willen weten of hun dak geschikt is, energiecoöperaties en gemeenten die een straat of wijk willen doorrekenen, en ontwikkelaars die een eerste schatting willen zonder een betaalde dienst.

Wat je krijgt

Een kleine tool die bij een adres vier dingen laat zien: het pand uit de BAG, de vorm van het dak uit het hoogtemodel AHN4 (dakvlakken per richting met hun helling en oppervlak), de zonnestraling van het dichtstbijzijnde KNMI-station en een ruwe schatting van wat panelen opleveren. Daarnaast het gemiddelde stroomverbruik per woning in de buurt uit de RVO-potentiekaart, zodat je de opbrengst ergens tegen kunt afzetten.

Proefdraai op 16 oktober 2026 met Kamperfoeliestraat 1 in De Bilt (pand uit 1987, grondvlak 68,1 m²): een zadeldak van ongeveer 45 graden met 39,4 m² dakvlak op het zuiden en 30,3 m² op het noorden, nok 9,1 meter boven het maaiveld (2,90 m NAP). Het KNMI-station De Bilt ligt op 0,6 km en mat in 2025 1.211 kWh zonnestraling per m² op een plat vlak; gemiddeld over 2016 tot en met 2025 was dat 1.100 kWh/m² (laagst 1.020 in 2017, hoogst 1.211 in 2025). Met vuistregels (70% van het zuidvlak bruikbaar, panelen met 21% rendement, prestatiefactor 0,8) komt dat neer op zo'n 28 m² panelen, ongeveer 5,8 kWp en circa 5.600 kWh per jaar. Huishoudens in de buurt De Bilt gebruikten volgens de RVO-kaart (cijfers 2014) ongeveer 3.050 kWh stroom per woning per jaar.

Dit is een schatting, geen offerte: schaduw van bomen en buren, de precieze dakhelling, dakkapellen en het type panelen zitten er niet goed in.

Zonnestraling op een plat vlak in De Bilt (KNMI-metingen): gemiddeld 1.038 kWh per m² per jaar in 1996–2025, 8% meer dan in 1958–1987

Zonnestraling op een plat vlak in De Bilt (KNMI-metingen), 1958 – 2025, in kWh per m² per jaar. Dat is het hoogste punt in de reeks.

Bron: KNMI, daggegevens van het weer · opgehaald 25 oktober 2026 · selectie: station 260 (De Bilt); Q, alleen volledige jarenOpen de tabel Los blok

Zo zou je kunnen starten7 stappen

  1. 1

    1. Van adres naar verblijfsobject (BAG-WFS)

    Zoek het adres op in de BAG-WFS van PDOK met een filter op postcode en huisnummer. Je krijgt het verblijfsobject met een punt in RD-coördinaten (EPSG:28992), het bouwjaar en de pandidentificatie. Gebruik niet de PDOK Locatieserver: robots.txt van api.pdok.nl sluit /bzk/locatieserver/* uit. Let op huisletters en toevoegingen: bij 12A en 12B krijg je meer dan één object, filter dan ook op huisletter of toevoeging.

    https://service.pdok.nl/lv/bag/wfs/v2_0?service=WFS&version=2.0.0&request=GetFeature&typeNames=bag:verblijfsobject&outputFormat=application/json&srsName=EPSG:28992&filter=<Filter><And><PropertyIsEqualTo><PropertyName>postcode</PropertyName><Literal>3732VT</Literal></PropertyIsEqualTo><PropertyIsEqualTo><PropertyName>huisnummer</PropertyName><Literal>1</Literal></PropertyIsEqualTo></And></Filter>
    "numberMatched": 1,
    "features": [
    { "type": "Feature", "properties": { "identificatie": "0310010000683233", "oppervlakte": 123,
      "status": "Verblijfsobject in gebruik", "gebruiksdoel": "woonfunctie", "openbare_ruimte": "Kamperfoeliestraat",
      "huisnummer": 1, "huisletter": "", "toevoeging": "", "postcode": "3732VT", "woonplaats": "De Bilt",
      "bouwjaar": 1987, "pandidentificatie": "0310100000667094", "pandstatus": "Pand in gebruik" },
      "geometry": { "type": "Point", "coordinates": [ 141103.0, 457452.0 ] } }
  2. 2

    2. De omtrek van het pand

    Haal met de pandidentificatie het pand op. De polygoon is de omtrek van het gebouw op de grond (in meters, RD). Daarmee bereken je het grondvlak (shoelace-formule: 68,1 m² hier) en knip je straks het hoogtemodel uit. Let op: een pand kan meerdere adressen hebben (aantal_verblijfsobjecten), bijvoorbeeld een flat; het dak is dan van iedereen.

    https://service.pdok.nl/lv/bag/wfs/v2_0?service=WFS&version=2.0.0&request=GetFeature&typeNames=bag:pand&outputFormat=application/json&srsName=EPSG:28992&filter=<Filter><PropertyIsEqualTo><PropertyName>identificatie</PropertyName><Literal>0310100000667094</Literal></PropertyIsEqualTo></Filter>
    { "type": "Feature", "properties": { "identificatie": "0310100000667094", "bouwjaar": 1987,
      "status": "Pand in gebruik", "gebruiksdoel": "woonfunctie", "oppervlakte_min": 123, "oppervlakte_max": 123,
      "aantal_verblijfsobjecten": 1 }, "bbox": [ 141098.73, 457448.896, 141109.192, 457462.932 ],
      "geometry": { "type": "Polygon", "coordinates": [ [ [ 141102.413, 457460.394 ], [ 141098.73, 457451.005 ],
        [ 141104.106, 457448.896 ], [ 141109.192, 457462.016 ], [ 141106.857, 457462.932 ],
        [ 141105.401, 457459.222 ], [ 141102.413, 457460.394 ] ] ] } }
  3. 3

    3. Snel één hoogte: AHN GetFeatureInfo (en waarom dat niet genoeg is)

    Een WMS-GetFeatureInfo op de laag dsm_05m (oppervlak met gebouwen) geeft de hoogte in meters NAP. Gebruik een bbox van 200 bij 200 meter met 200 pixels en klik in het midden (i=j=100); met een heel kleine bbox komt er een lege lijst terug. Twee valkuilen: de dienst geeft de dichtstbijzijnde cel met een waarde terug, met eigen coördinaten (hier 141101,75; 457454,25, ruim 2 meter naast het gevraagde punt), en het maaiveldmodel dtm_05m heeft onder gebouwen geen waarde. Voor de vorm van een dak heb je dus het raster zelf nodig (stap 4).

    https://service.pdok.nl/rws/ahn/wms/v1_0?service=WMS&version=1.3.0&request=GetFeatureInfo&layers=dsm_05m&query_layers=dsm_05m&styles=&crs=EPSG:28992&bbox=141003,457352,141203,457552&width=200&height=200&i=100&j=100&info_format=application/json
    {"type":"FeatureCollection","features":[{"type":"Feature","bbox":[141101.75,457454.25,141101.75,457454.25],
     "geometry":{"type":"Point","coordinates":[141101.75,457454.25]},"properties":{"value_list":"9.61400032"}}],
     "crs":{"properties":{"name":"urn:ogc:def:crs:EPSG::28992"},"type":"name"}}
  4. 4

    4. Het dak als raster: AHN WCS GetCoverage

    Knip met WCS een rechthoek uit rond het pand (bbox van de polygoon plus 3 meter), één keer uit dsm_05m (dak) en één keer uit dtm_05m (grond eromheen). Je krijgt een GeoTIFF met cellen van 0,5 meter, float32. Die is gecomprimeerd met deflate en een floating point predictor (Predictor 3): GDAL, rasterio of geotiff.js lezen dat zonder meer, een eigen lezer moet per rij eerst de bytes optellen en daarna ontvlechten (zie het script). Lege cellen hebben de waarde 3,4028235e38. Bereken per dakcel helling en kompasrichting uit de buurcellen, laat cellen steiler dan 60 graden weg (dakranden en gevels) en tel per richting het schuine oppervlak (0,25 m² gedeeld door de cosinus van de helling).

    https://service.pdok.nl/rws/ahn/wcs/v1_0?service=WCS&version=2.0.1&request=GetCoverage&coverageId=dsm_05m&subset=x(141095,141113)&subset=y(457445,457466)&format=image/tiff
    HTTP/1.1 200 OK
    Content-Length: 5041
    Content-Type: image/tiff
    (TIFF-kop, uitgelezen: breedte 36, hoogte 42 cellen, RowsPerStrip 42,
     Compression 8 = deflate, Predictor 3 = floating point, SampleFormat 3 = float)
  5. 5

    5. Zonnestraling van het dichtstbijzijnde KNMI-station

    De KNMI-daggegevens hebben per station de globale straling Q in J/cm² per dag: de zon op een plat vlak. Delen door 360 geeft kWh per m² (1 kWh is 3,6 miljoen joule, 1 m² is 10.000 cm²). Vraag eerst alle stations op zonder fmt: de tekstversie begint met een lijst van stations met lengte- en breedtegraad, zodat je het dichtstbijzijnde kunt kiezen. Niet elk station meet straling: in 2025 hadden 31 van de 48 stations een volledig jaar Q. Neem daarna een reeks van tien jaar van dat station, want één jaar zegt weinig: 2025 was het zonnigste jaar van de tien in De Bilt.

    curl -X POST "https://www.daggegevens.knmi.nl/klimatologie/daggegevens" \
      -d 'stns=260&vars=Q&start=20250101&end=20251231&fmt=json'
    [{"station_code":260,"date":"2025-01-01T00:00:00.000Z","Q":38},
     {"station_code":260,"date":"2025-01-02T00:00:00.000Z","Q":328},
     {"station_code":260,"date":"2025-01-03T00:00:00.000Z","Q":156},
     ...
     {"station_code":260,"date":"2025-12-31T00:00:00.000Z","Q":276}]
    (365 dagen, som 436.112 J/cm² = 1.211 kWh/m²)
  6. 6

    6. De stationslijst met coördinaten

    Zonder fmt krijg je tekst met commentaarregels. Daarin staan alle stations met LON en LAT. Reken het RD-punt van het adres om naar graden (of vraag het verblijfsobject op met srsName=EPSG:4326; de GeoJSON geeft dan lengte, breedte) en kies het dichtstbijzijnde station dat een vol jaar Q heeft.

    curl -X POST "https://www.daggegevens.knmi.nl/klimatologie/daggegevens" \
      -d 'stns=ALL&vars=Q&start=20251001&end=20251001'
    # STN         LON(east)   LAT(north)  ALT(m)      NAME
    # 209         4.518       52.465      0.00        IJmond
    # 210         4.430       52.171      -0.20       VALKENBURG VK
    # 260         5.180       52.100      1.90        De Bilt
    # 265         5.274       52.130      13.90       Soesterberg
    ...
    # Q         : Globale straling (in J/cm2) / Global radiation (in J/cm2)
    # STN,YYYYMMDD,    Q
      209,20251001,
  7. 7

    7. Het stroomverbruik in de buurt (RVO-potentiekaart)

    De potentiekaart omgevingswarmte van RVO heeft per buurt het aantal woningen, het oppervlak en het stroom- en warmteverbruik van huishoudens, plus het potentieel voor bodemwarmte (WKO). Vraag de buurt op met een kleine bbox rond het adres in RD (bbox-crs). Let op: de cijfers zijn van 2014 met de buurtindeling van 2014, en de API noemt geen eenheden. De verbruiksvelden staan in dezelfde eenheid als de potentiëlen (GJ per hectare per jaar): 75,68 × 584 ha / 4.026 woningen = 11,0 GJ = ongeveer 3.050 kWh per woning, een plausibel huishoudverbruik. Gebruik het als ijkpunt, niet als jouw verbruik.

    https://api.pdok.nl/rvo/potentiekaart-omgevingswarmte/ogc/v1/collections/warmtegeslotenwkobuurt/items?f=json&bbox=141102,457451,141104,457453&bbox-crs=http://www.opengis.net/def/crs/EPSG/0/28992&limit=5
    {"aantal_huishoudens_buurt": 4025, "aantal_inwoners_buurt": 8755, "aantal_woningen": 4026,
     "buurtnaam": "De Bilt", "cbs_buurtcode_1": "BU03100008", "gemeentenaam": "De Bilt", "jaar": 2014,
     "opp_land_totaal": 584, "potentieel_warmte_gesloten_wko": 1600, "potentieel_warmte_open_wko": 1900,
     "verbruik_elektra_huishoudens": 75.675514, "verbruik_warmte_huishoudens": 294.483198, ...}

Hoe de bronnen samenkomen

Laat AI het bouwen

Plak deze prompt in Claude, ChatGPT, Cursor of Lovable. Hij noemt de echte endpoints, de koppelsleutels en de valkuilen, zodat de assistent meteen werkende code schrijft. Controleer de uitkomst altijd met de bron zelf.

Bouw een webtool (Node/TypeScript backend + eenvoudige frontend, of Python met FastAPI) waarin ik een postcode en huisnummer intyp en zie hoe geschikt mijn dak is voor zonnepanelen en wat ze ruwweg opleveren. Alle bronnen zijn open en zonder sleutel. Stuur in elke aanroep een eerlijke User-Agent mee, zoals "mijn-zonproject/1.0 (+jij@voorbeeld.nl)".

Stap 1, adres naar pand (BAG via PDOK WFS, niet de Locatieserver: die is in robots.txt van api.pdok.nl uitgesloten):
https://service.pdok.nl/lv/bag/wfs/v2_0?service=WFS&version=2.0.0&request=GetFeature&typeNames=bag:verblijfsobject&outputFormat=application/json&srsName=EPSG:28992&filter=<Filter><And><PropertyIsEqualTo><PropertyName>postcode</PropertyName><Literal>3732VT</Literal></PropertyIsEqualTo><PropertyIsEqualTo><PropertyName>huisnummer</PropertyName><Literal>1</Literal></PropertyIsEqualTo></And></Filter>
- Velden: identificatie, postcode, huisnummer, huisletter, toevoeging, bouwjaar, pandidentificatie, geometry (punt in RD).
- Meer dan één treffer bij huisletters of toevoegingen (12A, 12B): laat de gebruiker kiezen of filter op huisletter/toevoeging.
- Daarna het pand: typeNames=bag:pand met een filter op identificatie = pandidentificatie. Geometry is de polygoon van het grondvlak; aantal_verblijfsobjecten > 1 betekent een gedeeld dak (flat): toon dat.

Stap 2, dakvorm uit AHN4 (PDOK WCS, cellen van 0,5 m, CC0):
https://service.pdok.nl/rws/ahn/wcs/v1_0?service=WCS&version=2.0.1&request=GetCoverage&coverageId=dsm_05m&subset=x(141095,141113)&subset=y(457445,457466)&format=image/tiff
- Neem de bbox van de pandpolygoon plus 3 meter. Haal dsm_05m (dak) en dtm_05m (maaiveld) op.
- De GeoTIFF is float32, deflate, Predictor 3 (floating point). Gebruik geotiff.js of rasterio; zelf lezen kan ook (bytes per rij cumulatief optellen, dan per cel de 4 bytes uit 4 blokken van breedte w halen, big-endian).
- Nodata = 3.4028235e38. Het DTM is leeg onder gebouwen: neem de mediaan van de DTM-cellen buiten de polygoon als maaiveld.
- Per dakcel binnen de polygoon: helling en kompasrichting uit de vier buurcellen (dz/dx en dz/dy, y wijst naar het noorden). Laat cellen steiler dan 60 graden weg (randen, gevels). Klassen: plat (< 10 graden), zuid (135 tot 225), oost (45 tot 135), west (225 tot 315), noord. Schuin oppervlak per cel = 0,25 / cos(helling).
- WMS GetFeatureInfo is niet geschikt voor de dakvorm: het geeft de dichtstbijzijnde cel met een waarde terug, soms meters naast het gevraagde punt.
- Het DSM is het hoogste punt: overhangende bomen tellen mee als dak. AHN4 is ingewonnen in 2020 tot en met 2022: nieuwere dakkapellen of uitbouwen ontbreken.

Stap 3, zonnestraling (KNMI daggegevens, POST, CC BY 4.0):
POST https://www.daggegevens.knmi.nl/klimatologie/daggegevens met body stns=ALL&vars=Q&start=20250101&end=20251231 (zonder fmt: tekst met stationslijst LON/LAT in de kop).
- Q is globale straling in J/cm² per dag op een plat vlak; kWh/m² = Q / 360.
- Niet elk station meet Q (31 van 48 hadden in 2025 een vol jaar). Kies het dichtstbijzijnde station met minstens 360 dagen.
- Haal daarna tien jaar op voor dat station (fmt=json, start=20160101, end=20251231) en reken met het gemiddelde; laat ook het hoogste en laagste jaar zien. In De Bilt: 1.100 kWh/m² gemiddeld, 1.020 tot 1.211.
- Ruwe stationsreeksen: niet gehomogeniseerd, niet bedoeld voor trends.

Stap 4, opbrengst als schatting (maak de aannames instelbaar en zet ze zichtbaar op het scherm):
opbrengst (kWh/jaar) = straling (kWh/m²) × richtingsfactor × paneeloppervlak (m²) × rendement × prestatiefactor
- Startwaarden: rendement 0,21, prestatiefactor 0,8, 70% van een dakvlak bruikbaar, richtingsfactor zuid 1,1, oost en west 0,9, plat 1,0, noord niet meetellen. Dit zijn vuistregels, geen metingen.
- Toon kWp (paneeloppervlak × rendement) en kWh per jaar, afgerond. Zet er duidelijk "schatting" bij en dat schaduw niet is meegerekend.
- PVGIS (JRC) rekent dit preciezer uit, maar robots.txt van re.jrc.ec.europa.eu verbiedt alle geautomatiseerde aanroepen: link alleen naar de webtool, roep de API niet aan.

Stap 5, ijkpunt verbruik (RVO-potentiekaart, CC0):
https://api.pdok.nl/rvo/potentiekaart-omgevingswarmte/ogc/v1/collections/warmtegeslotenwkobuurt/items?f=json&bbox=<x-1>,<y-1>,<x+1>,<y+1>&bbox-crs=http://www.opengis.net/def/crs/EPSG/0/28992&limit=1
- Stroom per woning (kWh) = verbruik_elektra_huishoudens × opp_land_totaal × 1e9 / 3,6e6 / aantal_woningen. De API noemt geen eenheid; GJ per hectare per jaar geeft plausibele uitkomsten (~3.050 kWh in De Bilt).
- Cijfers en buurtindeling van 2014. Zeg dat erbij.

Uitkomst: één pagina met het adres, een plattegrond van het dak gekleurd naar richting, een tabel met dakvlakken (richting, helling, m²), de straling van het gekozen station (gemiddeld en bereik), de geschatte kWp en kWh per jaar, en het buurtverbruik als vergelijking. Bronvermelding: Kadaster/PDOK (BAG), AHN via PDOK, KNMI, RVO.

Probeer het zelf

De kern van het project in een klein script. Nodig: Node 20+ (fetch en zlib zitten erin, geen pakketten nodig). Opslaan als zon.mjs, draaien met: node zon.mjs 3732VT 1. Getest op 2026-10-16; hieronder de echte uitvoer van die proefdraai.

// Zonnepanelen-check: hoeveel zon valt er op je dak en wat levert het ruwweg op?
// Bronnen: BAG (PDOK WFS), AHN4 (PDOK WCS), KNMI daggegevens (globale straling Q), RVO-potentiekaart (buurt).
// Node 20+, geen pakketten nodig. Gebruik: node zon.mjs 3732VT 1
import { inflateSync } from "node:zlib";

const UA = { "User-Agent": "mijn-zonproject/1.0 (+jij@voorbeeld.nl)" };
const [POSTCODE = "3732VT", HUISNUMMER = "1"] = process.argv.slice(2);
const JAAR = 2025; // laatste volle jaar

// Aannames (vuistregels, geen metingen): pas ze aan voor jouw situatie.
const RENDEMENT = 0.21;       // moderne panelen: ~21% van het licht wordt stroom (≈ 0,21 kWp per m² paneel)
const PRESTATIE = 0.8;        // prestatiefactor: verlies door omvormer, kabels, warmte, vuil
const BRUIKBAAR = 0.7;        // deel van een geschikt dakvlak waar echt panelen passen (randen, schoorsteen, dakraam)
const FACTOR = { zuid: 1.1, oost: 0.9, west: 0.9, plat: 1.0 }; // straling op het paneel t.o.v. een plat vlak

const get = async (url, opts = {}) => {
  const r = await fetch(url, { ...opts, headers: { ...UA, ...(opts.headers ?? {}) } });
  if (!r.ok) throw new Error(`${r.status} ${url}`);
  return r;
};
const fes = (filters) => "<Filter>" + (filters.length > 1 ? "<And>" : "") +
  filters.map(([k, v]) => `<PropertyIsEqualTo><PropertyName>${k}</PropertyName><Literal>${v}</Literal></PropertyIsEqualTo>`).join("") +
  (filters.length > 1 ? "</And>" : "") + "</Filter>";
const wfs = async (laag, filters) => {
  const u = new URL("https://service.pdok.nl/lv/bag/wfs/v2_0");
  Object.entries({ service: "WFS", version: "2.0.0", request: "GetFeature", typeNames: laag,
    outputFormat: "application/json", srsName: "EPSG:28992", filter: fes(filters) }).forEach(([k, v]) => u.searchParams.set(k, v));
  return (await (await get(u)).json()).features;
};

// 1. Adres → verblijfsobject → pand (BAG-WFS; de Locatieserver is in robots.txt uitgesloten)
const [vbo] = await wfs("bag:verblijfsobject", [["postcode", POSTCODE.toUpperCase().replace(/\s/g, "")], ["huisnummer", HUISNUMMER]]);
if (!vbo) throw new Error("adres niet gevonden");
const a = vbo.properties;
const [pand] = await wfs("bag:pand", [["identificatie", a.pandidentificatie]]);
const ring = pand.geometry.coordinates[0];
let opp = 0;
for (let i = 0; i < ring.length - 1; i++) opp += ring[i][0] * ring[i + 1][1] - ring[i + 1][0] * ring[i][1];
console.log(`${a.openbare_ruimte} ${a.huisnummer}${a.huisletter}, ${a.postcode} ${a.woonplaats}`);
console.log(`  pand ${a.pandidentificatie}, bouwjaar ${pand.properties.bouwjaar}, ${pand.properties.aantal_verblijfsobjecten} adres(sen) in het pand`);
console.log(`  grondvlak volgens BAG-omtrek: ${Math.abs(opp / 2).toFixed(1)} m²`);

// 2. AHN4: oppervlakte (DSM) en maaiveld (DTM) rond het pand, 0,5 m cellen, GeoTIFF met deflate + float-predictor
function leesTiff(buf) {
  const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
  const tags = {};
  const n = dv.getUint16(dv.getUint32(4, true), true);
  for (let i = 0; i < n; i++) {
    const p = dv.getUint32(4, true) + 2 + i * 12;
    const tag = dv.getUint16(p, true), type = dv.getUint16(p + 2, true), cnt = dv.getUint32(p + 4, true);
    const size = { 3: 2, 4: 4, 12: 8 }[type] ?? 1;
    const off = cnt * size > 4 ? dv.getUint32(p + 8, true) : p + 8;
    tags[tag] = Array.from({ length: cnt }, (_, k) =>
      type === 3 ? dv.getUint16(off + 2 * k, true) : type === 4 ? dv.getUint32(off + 4 * k, true) : type === 12 ? dv.getFloat64(off + 8 * k, true) : dv.getUint8(off + k));
  }
  const w = tags[256][0], h = tags[257][0], rps = tags[278]?.[0] ?? h;
  if (tags[322]) throw new Error("tegel-TIFF niet ondersteund");
  const waarden = new Float32Array(w * h);
  tags[273].forEach((off, s) => {
    let ruw = buf.subarray(off, off + tags[279][s]);
    if (tags[259][0] === 8) ruw = inflateSync(ruw);
    for (let r = 0; r < rps && s * rps + r < h; r++) {
      const rij = Uint8Array.from(ruw.subarray(r * w * 4, (r + 1) * w * 4));
      if (tags[317]?.[0] === 3) {             // floating point predictor: bytes optellen, dan ontvlechten
        for (let k = 1; k < rij.length; k++) rij[k] = (rij[k] + rij[k - 1]) & 255;
        for (let c = 0; c < w; c++)
          waarden[(s * rps + r) * w + c] = new DataView(Uint8Array.of(rij[c], rij[w + c], rij[2 * w + c], rij[3 * w + c]).buffer).getFloat32(0, false);
      } else for (let c = 0; c < w; c++) waarden[(s * rps + r) * w + c] = new DataView(rij.buffer).getFloat32(c * 4, true);
    }
  });
  const [sx, sy] = tags[33550], tp = tags[33922];
  return { w, h, waarden, x0: tp[3], y0: tp[4], sx, sy };
}
const xs = ring.map((p) => p[0]), ys = ring.map((p) => p[1]);
const bb = [Math.floor(Math.min(...xs)) - 3, Math.floor(Math.min(...ys)) - 3, Math.ceil(Math.max(...xs)) + 3, Math.ceil(Math.max(...ys)) + 3];
const wcs = async (laag) => {
  const r = await get(`https://service.pdok.nl/rws/ahn/wcs/v1_0?service=WCS&version=2.0.1&request=GetCoverage&coverageId=${laag}` +
    `&subset=x(${bb[0]},${bb[2]})&subset=y(${bb[1]},${bb[3]})&format=image/tiff`);
  return leesTiff(Buffer.from(await r.arrayBuffer()));
};
const dsm = await wcs("dsm_05m"), dtm = await wcs("dtm_05m");
const binnen = (x, y) => { let ja = false; for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
  const [xi, yi] = ring[i], [xj, yj] = ring[j];
  if ((yi > y) !== (yj > y) && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi) ja = !ja; } return ja; };
const geldig = (v) => v < 1e30;
const mediaan = (l) => { const s = [...l].sort((p, q) => p - q); return s[Math.floor(s.length / 2)]; };
const grond = [], dak = [];
for (let r = 1; r < dsm.h - 1; r++) for (let c = 1; c < dsm.w - 1; c++) {
  const x = dsm.x0 + (c + 0.5) * dsm.sx, y = dsm.y0 - (r + 0.5) * dsm.sy, i = r * dsm.w + c;
  if (!binnen(x, y)) { if (geldig(dtm.waarden[i])) grond.push(dtm.waarden[i]); continue; }
  const z = (rr, cc) => dsm.waarden[rr * dsm.w + cc];
  if (![z(r, c), z(r, c - 1), z(r, c + 1), z(r - 1, c), z(r + 1, c)].every(geldig)) continue;
  const dzdx = (z(r, c + 1) - z(r, c - 1)) / (2 * dsm.sx), dzdy = (z(r - 1, c) - z(r + 1, c)) / (2 * dsm.sy); // y naar noord
  const helling = (Math.atan(Math.hypot(dzdx, dzdy)) * 180) / Math.PI;
  const richting = ((Math.atan2(-dzdx, -dzdy) * 180) / Math.PI + 360) % 360; // kompasrichting waar het vlak naar kijkt
  dak.push({ z: z(r, c), helling, richting });
}
const maaiveld = mediaan(grond);
const randloos = dak.filter((d) => d.helling < 60); // steile cellen zijn dakranden en gevels
const klas = (d) => d.helling < 10 ? "plat" : d.richting >= 135 && d.richting < 225 ? "zuid" : d.richting >= 45 && d.richting < 135 ? "oost"
  : d.richting >= 225 && d.richting < 315 ? "west" : "noord";
const vlakken = {};
for (const d of randloos) { const k = klas(d); (vlakken[k] ??= { cellen: 0, opp: 0, helling: [] }); vlakken[k].cellen++;
  vlakken[k].opp += (dsm.sx * dsm.sy) / Math.cos((d.helling * Math.PI) / 180); vlakken[k].helling.push(d.helling); }
console.log(`\nAHN4 (2020-2022): maaiveld ${maaiveld.toFixed(2)} m NAP, nok ${(Math.max(...dak.map((d) => d.z)) - maaiveld).toFixed(1)} m boven maaiveld, ${dak.length} dakcellen van 0,5 m`);
for (const [k, v] of Object.entries(vlakken).sort((p, q) => q[1].opp - p[1].opp))
  console.log(`  ${k.padEnd(5)} ${v.opp.toFixed(1).padStart(6)} m² dakvlak, helling ~${mediaan(v.helling).toFixed(0)}°`);

// 3. KNMI: globale straling Q (J/cm² per dag) van het dichtstbijzijnde station dat Q meet; kWh/m² = Q / 360
const tekst = await (await get("https://www.daggegevens.knmi.nl/klimatologie/daggegevens", { method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: `stns=ALL&vars=Q&start=${JAAR}0101&end=${JAAR}1231` })).text();
const stations = {}, som = {}, dagen = {};
for (const regel of tekst.split("\n")) {
  const m = regel.match(/^# (\d{3})\s+([\d.]+)\s+([\d.]+)\s+\S+\s+(.+?)\s*$/);
  if (m) stations[m[1]] = { lon: +m[2], lat: +m[3], naam: m[4] };
  const d = regel.match(/^\s*(\d{3}),\d{8},\s*(\d+)\s*$/);
  if (d) { som[d[1]] = (som[d[1]] ?? 0) + +d[2]; dagen[d[1]] = (dagen[d[1]] ?? 0) + 1; }
}
// RD → graden (benadering van Schreutelkamp en Strang van Hees, nauwkeurig tot ~1 m)
const [X, Y] = vbo.geometry.coordinates, dX = (X - 155000) * 1e-5, dY = (Y - 463000) * 1e-5;
const lat = 52.1551744 + (3235.65389 * dY - 32.58297 * dX ** 2 - 0.2475 * dY ** 2 - 0.84978 * dX ** 2 * dY - 0.0655 * dY ** 3 - 0.01709 * dX ** 2 * dY ** 2 + 0.00738 * dX + 0.0053 * dX ** 4) / 3600;
const lon = 5.38720621 + (5260.52916 * dX + 105.94684 * dX * dY + 2.45656 * dX * dY ** 2 - 0.81885 * dX ** 3 + 0.05594 * dX * dY ** 3 - 0.05607 * dX ** 3 * dY + 0.01199 * dY - 0.00256 * dX ** 3 * dY ** 2) / 3600;
const km = (s) => Math.hypot((s.lon - lon) * 111.32 * Math.cos((lat * Math.PI) / 180), (s.lat - lat) * 110.57);
const [stn] = Object.keys(som).filter((s) => dagen[s] >= 360).sort((p, q) => km(stations[p]) - km(stations[q]));
// Tien jaar van dat station: een zonnig jaar is geen gewoon jaar
const reeks = await (await get("https://www.daggegevens.knmi.nl/klimatologie/daggegevens", { method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: `stns=${stn}&vars=Q&start=${JAAR - 9}0101&end=${JAAR}1231&fmt=json` })).json();
const perJaar = {};
for (const r of reeks) if (r.Q != null) perJaar[r.date.slice(0, 4)] = (perJaar[r.date.slice(0, 4)] ?? 0) + r.Q / 360;
const jaren = Object.values(perJaar), kwh = jaren.reduce((p, q) => p + q, 0) / jaren.length;
const [laag, hoog] = [Math.min(...jaren), Math.max(...jaren)].map(Math.round);
console.log(`\nKNMI ${stations[stn].naam.trim()} (${stn}, ${km(stations[stn]).toFixed(1)} km): zon op een plat vlak`);
console.log(`  ${JAAR}: ${Math.round(som[stn] / 360)} kWh/m², gemiddeld ${JAAR - 9}-${JAAR}: ${Math.round(kwh)} kWh/m² (${laag} tot ${hoog})`);

// 4. Ruwe schatting van de opbrengst (vuistregels hierboven; geen schaduw van bomen of buren meegerekend)
let totaal = 0;
console.log(`\nSchatting met het gemiddelde jaar (rendement ${RENDEMENT}, prestatiefactor ${PRESTATIE}, ${BRUIKBAAR * 100}% van het dakvlak):`);
for (const [k, v] of Object.entries(vlakken)) {
  if (!(k in FACTOR) || v.opp < 5) continue;
  const m2 = v.opp * BRUIKBAAR, kwp = m2 * RENDEMENT, opbrengst = kwh * FACTOR[k] * m2 * RENDEMENT * PRESTATIE;
  totaal += opbrengst;
  console.log(`  ${k.padEnd(5)} ${m2.toFixed(0).padStart(3)} m² panelen (~${kwp.toFixed(1)} kWp) -> ~${Math.round(opbrengst / 10) * 10} kWh per jaar`);
}
console.log(`  samen ~${Math.round(totaal / 100) * 100} kWh per jaar (schatting)`);

// 5. RVO-potentiekaart: stroomverbruik van huishoudens in de buurt (cijfers 2014, GJ per ha land per jaar)
const rvo = await (await get(`https://api.pdok.nl/rvo/potentiekaart-omgevingswarmte/ogc/v1/collections/warmtegeslotenwkobuurt/items?f=json` +
  `&bbox=${X - 1},${Y - 1},${X + 1},${Y + 1}&bbox-crs=http://www.opengis.net/def/crs/EPSG/0/28992&limit=1`)).json();
const b = rvo.features[0]?.properties;
if (b) {
  const perWoning = (b.verbruik_elektra_huishoudens * b.opp_land_totaal * 1e9) / 3.6e6 / b.aantal_woningen;
  console.log(`\nRVO-buurt ${b.buurtnaam} (${b.cbs_buurtcode_1}, indeling ${b.jaar}): ~${Math.round(perWoning)} kWh stroom per woning per jaar`);
  console.log(`  bodemwarmte gesloten WKO: ${b.potentieel_warmte_gesloten_wko} GJ/ha/jaar, warmteverbruik huishoudens ${Math.round(b.verbruik_warmte_huishoudens)} GJ/ha/jaar`);
}
Uitvoer
Kamperfoeliestraat 1, 3732VT De Bilt
  pand 0310100000667094, bouwjaar 1987, 1 adres(sen) in het pand
  grondvlak volgens BAG-omtrek: 68.1 m²

AHN4 (2020-2022): maaiveld 2.90 m NAP, nok 9.1 m boven maaiveld, 272 dakcellen van 0,5 m
  zuid    39.4 m² dakvlak, helling ~45°
  noord   30.3 m² dakvlak, helling ~45°
  plat     2.8 m² dakvlak, helling ~3°
  west     2.3 m² dakvlak, helling ~54°
  oost     0.3 m² dakvlak, helling ~31°

KNMI De Bilt (260, 0.6 km): zon op een plat vlak
  2025: 1211 kWh/m², gemiddeld 2016-2025: 1100 kWh/m² (1020 tot 1211)

Schatting met het gemiddelde jaar (rendement 0.21, prestatiefactor 0.8, 70% van het dakvlak):
  zuid   28 m² panelen (~5.8 kWp) -> ~5600 kWh per jaar
  samen ~5600 kWh per jaar (schatting)

RVO-buurt De Bilt (BU03100008, indeling 2014): ~3049 kWh stroom per woning per jaar
  bodemwarmte gesloten WKO: 1600 GJ/ha/jaar, warmteverbruik huishoudens 294 GJ/ha/jaar

Waar het misgaat

  • Gebruik de PDOK Locatieserver niet voor het opzoeken van adressen: robots.txt van api.pdok.nl sluit /bzk/locatieserver/* uit. De BAG-WFS met een FES-filter op postcode en huisnummer werkt wel en gaf het adres in een fractie van een seconde. Bij huisletters en toevoegingen komt er meer dan één object terug.
  • AHN GetFeatureInfo geeft niet precies de cel die je vraagt, maar de dichtstbijzijnde cel met een waarde, met eigen coördinaten (in de proef ruim 2 meter verderop). Het maaiveldmodel dtm_05m is leeg onder gebouwen. Voor een dakvorm heb je het raster uit WCS nodig.
  • De WCS-GeoTIFF is gecomprimeerd met deflate en Predictor 3 (floating point). Een eigen lezer die alleen inflate doet, geeft onzinwaarden. Lege cellen zijn 3,4028235e38: filter ze weg voor je hoogtes of hellingen berekent, anders wordt de nok 10^38 meter hoog.
  • Het oppervlaktemodel (DSM) is het hoogste punt: bomen die over het dak hangen tellen mee als dak en geven vreemde hellingen. PDOK heeft AHN4 (ingewonnen 2020 tot en met 2022), dus een dakkapel of uitbouw van daarna ontbreekt. Schaduw van bomen en buren rekent het script niet uit.
  • KNMI geeft Q in J/cm² per dag, niet in kWh: deel door 360. Niet elk station meet straling (31 van 48 met een vol jaar in 2025); stations zonder Q geven lege waarden, geen fout. Reken met een gemiddelde over meer jaren: in De Bilt lag de jaarsom tussen 2016 en 2025 tussen 1.020 en 1.211 kWh/m², bijna 20% verschil.
  • De opbrengst is een schatting met vuistregels (richtingsfactoren, rendement 21%, prestatiefactor 0,8, 70% van het dakvlak bruikbaar). Zet ze zichtbaar en instelbaar in je tool. PVGIS van het JRC rekent per helling en richting veel preciezer, maar robots.txt van re.jrc.ec.europa.eu verbiedt alle geautomatiseerde aanroepen: verwijs gebruikers naar de webtool en roep de API niet vanuit je code aan.
  • De RVO-potentiekaart heeft cijfers en buurtgrenzen van 2014 en noemt geen eenheden. De publicatiedatum op PDOK (2025) is niet de peildatum. Zoek de buurt op met het punt van het adres, niet met een huidige buurtcode.

Verder bouwen

  • Een straat of buurt doorrekenen: haal alle panden in een bbox op uit de BAG-WFS (bag:pand) en draai de dakanalyse per pand. Kleur een kaart naar geschatte kWh per pand en zoek de daken die nog leeg zijn.
  • Opbrengst per maand: tel Q per maand op in plaats van per jaar. In De Bilt viel er gemiddeld over 2016 tot en met 2025 in juni 168 kWh/m² en in december 15 kWh/m², ruim elf keer zo weinig. Dat telt voor wie een thuisbatterij of warmtepomp overweegt.
  • Schaduw inschatten: haal een groter stuk DSM op (bijvoorbeeld 60 bij 60 meter) en bereken voor een paar zonnestanden per seizoen of omliggende bomen en gebouwen hoger staan dan de zonnestraal over het dak.
  • Vergelijk met de werkelijkheid: CBS publiceert het opgestelde vermogen aan zonnepanelen per gemeente. Zet de geschatte dakpotentie van een buurt naast wat er al ligt.

Meer met deze data

Vermogen van alle zonnepanelen samen: 25,9 gigawatt in 2025, 288 keer zo veel als in 2010

Vermogen van alle zonnepanelen samen, 2010 – 2025, in gigawatt. Dat is het hoogste punt in de reeks.

Bron: CBS, tabel 82610NED (Hernieuwbare elektriciteit; productie en vermogen) · opgehaald 25 oktober 2026Open de tabel Los blok

Woningen met zonnepanelen: het hoogst in Tynaarlo (65%), het laagst in Amsterdam (9%)

Woningen met zonnepanelen per gemeente, 2024, in procent van de woningen. Heel Nederland: 36. 342 gemeenten met een cijfer.

Bron: CBS, tabel 85984NED (Kerncijfers wijken en buurten 2024) · opgehaald 25 oktober 2026Open de tabel Los blok

De bronnen4

Basisregistratie Adressen en Gebouwen (BAG)

Kadaster, via PDOKNederland, adres en gebouw

Alle adressen en gebouwen van Nederland met bouwjaar, oppervlakte en gebruiksdoel. De ruggengraat voor alles wat op adresniveau gekoppeld moet worden.

OpenKaartlaagAPIDownloadWonen en ruimte
Bereikbaar
dagelijks

Actueel Hoogtebestand Nederland (AHN)

Het Waterschapshuis namens waterschappen, provincies en Rijk; via PDOKNederland, 0,5 meter

De hoogtekaart van Nederland, gemeten met een laser vanuit een vliegtuig: minstens tien metingen per vierkante meter. Er zijn twee kaarten. Het maaiveldmodel (DTM) is de kale grond. Het oppervlaktemodel (DSM) is alles wat erop staat, met gebouwen en bomen. Via PDOK als kaartlaag (WMS), als API voor rasters (WCS) en als download.

OpenKaartlaagDownloadWonen en ruimteKlimaat en milieu
Actueel
bijgewerkt in april 2023

KNMI daggegevens van het weerUitgewerkt

KNMINederland, weerstation

Dagwaarden van ruim vijftig weerstations: temperatuur, neerslag, zon en wind. De reeks van De Bilt begint in 1901.

OpenAPIDownloadKlimaat en milieu
Actueel
bijgewerkt 3 dagen geleden

Potentiekaarten warmte (WarmteAtlas)

Rijksdienst voor Ondernemend Nederland (RVO), WarmteAtlas Nederland, via PDOKNederland, buurt (indeling 2014)

Vier kaartdiensten uit de WarmteAtlas van RVO: het potentieel voor warmte- en koudeopslag in de bodem (WKO) per buurt en gemeente, biogas en houtige biomassa per gemeente, een kaart van 2.283 bedrijfslocaties met hun CO2-uitstoot voor het inschatten van restwarmte, en een rasterkaart van het WKO-potentieel in GJ per hectare per jaar. Let op de leeftijd: de buurt- en gemeentecijfers hebben jaar 2014 (403 gemeenten, de indeling van toen) en de CO2-cijfers lopen tot 2013. De datums op PDOK (2024 en 2025) zijn publicatiedatums, geen peildatums.

OpenAPIKaartlaagEnergieKlimaat en milieu
Actueel
bijgewerkt in oktober 2025

Meer om te bouwen