Onafhankelijke vliegtuiggeluid- en radarwaarneming

Hinder Minder Schiphol

Onze missie: eerlijke inzichten in luchtverkeerslawaai
Bijgewerkt
Wetenschappelijke onderbouwing

Formules

Hier vindt u alle wiskundige formules, berekeningsmethodes en drempelwaarden die Hinder Minder Schiphol gebruikt om vlieglawaai te meten, te analyseren en te koppelen aan vliegtuigen. Onze methodologie is volledig transparant zodat u kunt begrijpen hoe we tot onze inzichten komen - van de locatie van een meetpunt tot de berekening van Lden en Lnight volgens de Europese Richtlijn Lucht Geluid.

Wall of Shame Composite Score

Het score dat de Wall of Shame rangschikking aandrijft. Het combineert drie factoren: het totale aantal ≥80 dB gebeurtenissen, hoe ver het gemiddelde dB boven de 80 dB drempel uitkomt, en hoe lang het vliegtuig al in overtreding is.

score=(event_count×Wevents)+(max(0,avg_dB80)×Wintensity)+(min(days_active,30)×Wseniority)\mathrm{score} = (\mathrm{event\_count} \times W_{\mathrm{events}}) + (\max(0, \mathrm{avg\_dB} - 80) \times W_{\mathrm{intensity}}) + (\min(\mathrm{days\_active}, 30) \times W_{\mathrm{seniority}})
W_events = 100, W_intensity = 10, W_seniority = 5
Wall of Shame composite score: 1000 + 20 + 150 = 117010 × 100events+150seniorityscore = 1170events × 100intensity × 10seniority × 5
Aircraft A: 10 events · 82.0 dB avg · 30 days active

In de diagram: vliegtuig A (10 gebeurtenissen, gem. 82,0 dB, 30 dagen actief) krijgt een score van 1170. De drie segmenten vertegenwoordigen de drie termen — gebeurtenissen (blauw), intensiteit (oranje) en anciënniteit (groen). Het score wordt per vliegtuig in Python berekend nadat Postgres de aggregaten (event_count, avg_dB, first_seen) heeft geleverd.

SymboolBronBeschrijving
event_countSQL: COUNT(dispute_events.id) GROUP BY aircraft_icao_hexTotaal aantal ≥80 dB gebeurtenissen toegeschreven aan dit vliegtuig.
avg_dBSQL: AVG(dispute_events.dB) GROUP BY aircraft_icao_hexGemiddelde dB-waarde over alle gebeurtenissen van dit vliegtuig.
first_seenSQL: MIN(dispute_events.event_ts) GROUP BY aircraft_icao_hexTijdstip van de eerste gebeurtenis voor dit vliegtuig.
days_activenow − first_seenAantal dagen sinds de eerste gebeurtenis (max 30).

Noise↔Aircraft Spatial Join (Haversine)

Standaard Haversine-formule voor de grootcirkelafstand tussen het meetpunt (52,373679° N, 4,753564° E — Zwanenburg / Olmenlaan, meetpunt 2) en de ADS-B-positie van het vliegtuig. Het resultaat wordt opgeslagen als aircraft_dist_km in de tabel dispute_events.

a=sin2 ⁣(Δlat2)+cos(lat1)cos(lat2)sin2 ⁣(Δlon2)c=2asin(a)d=Rc\begin{aligned} a &= \sin^2\!\left(\frac{\Delta\mathrm{lat}}{2}\right) + \cos(\mathrm{lat}_1)\,\cos(\mathrm{lat}_2)\,\sin^2\!\left(\frac{\Delta\mathrm{lon}}{2}\right) \\ c &= 2\,\mathrm{asin}(\sqrt{a}) \\ d &= R \cdot c \end{aligned}
R = 6371 km (gemiddelde aardstraal)
Great-circle distance d = R · 2 · asin(√a)ORdmeetpunt(lat₁, lon₁)aircraft(lat₂, lon₂)Δσd = R · 2 · asin(√a) (R = 6371 km)
Meetpunt 2 (Zwanenburg / Olmenlaan, 52.373679° N, 4.753564° E) and an aircraft ~6 km north-west.

De variabelen lat1,lon1\mathrm{lat}_1, \mathrm{lon}_1 zijn de meetpuntcoördinaten (hard-coded in de code), lat2,lon2\mathrm{lat}_2, \mathrm{lon}_2 komen uit dump1090_aircraft_lat/dump1090_aircraft_lon, en Δlat,Δlon\Delta\mathrm{lat}, \Delta\mathrm{lon} zijn de verschillen in radialen. Het resultaat wordt afgerond op 3 decimalen bij het schrijven naar de database.

SymboolBronBeschrijving
RconstanteAardstraal — 6371,0 km.
lat₁, lon₁get_meetpunt_coords(meetpunt_id)Meetpuntcoördinaten — standaard voor meetpunt 2 (Zwanenburg / Olmenlaan): 52,373679° N, 4,753564° E. In productie worden de coördinaten uit de PostgreSQL-tabel <code>meetpunten</code> gelezen.
lat₂, lon₂dump1090_aircraft_lat, dump1090_aircraft_lonADS-B-positie van het vliegtuig.
duitvoerGrootcirkelafstand in km; opgeslagen als aircraft_dist_km.

Noise↔Aircraft Temporal Matching

Per ≥80 dB gebeurtenis zoekt de API het dichtstbijzijnde vliegtuig op basis van de genormaliseerde correlatiescore die tijd en afstand combineert via een referentiesnelheid. Het vliegtuig met de laagste score wint. Het matchingvenster is ±30s — breed genoeg om ook vliegtuigen in de lange eindnadering te kunnen vangen.

score=Δt+dslantvref\mathrm{score} = |\Delta t| + \frac{d_{\mathrm{slant}}}{v_{\mathrm{ref}}}
score = |Δt| + d_slant / v_ref (v_ref = 0.077 km/s)
Temporal matching: PH-NXI wins with score 6.501020PH-NXI6.5 ✓G-EZAA20.0score = |Δt| + Haversine distance
PH-NXI: Δt = 2.0 s, dist = 4.5 km → score 6.5 (winner)
G-EZAA: Δt = 8.0 s, dist = 12.0 km → score 20.0

De variabele dslantd_{\mathrm{slant}} is de schuine afstand met hoogtecorrectie, en vref=0.077  km/sv_{\mathrm{ref}} = 0.077\;\mathrm{km/s} is de referentiesnelheid (~278 km/h, typische naderingssnelheid voor EHAM). De score normaliseert de tijd- en afstandsfout zodat "1 seconde er naast" even zwaar telt als "1 kilometer er naast".

Beperkingen: alleen vliegtuigposities binnen ±30 seconden van het event-tijdstip komen in aanmerking (_WINDOW_SECONDS = 30.0). Als geen enkel vliegtuig binnen dat venster valt, blijft de gebeurtenis ongekoppeld ( aircraft_icao_hex = NULL). De kolom aircraft_dist_km in dispute_events slaat de schuine afstand op (zie Slant Distance Correction), niet de gecombineerde score — het tijdsverschil wordt alleen voor de matching gebruikt.

SymboolBronBeschrijving
Δt|ts_aircraft − ts_event|Absolute tijdsverschil in seconden tussen het ADS-B sample en de noise-event timestamp.
d_slantslant_distance() in geo.pySchuine afstand (Haversine + hoogtecorrectie) in km — zie Slant Distance Correction.
v_refREFERENCE_VELOCITY_KM_PER_S = 0.077Referentiesnelheid in km/s (~278 km/h). Normaliseert afstand tot tijdsequivalent.
_WINDOW_SECONDSenrich.py, sampler.pyMatchingvenster: ±30s.

Meetpunt Golden-angle Palette

Voor tot 45 meetpuntlijnen op de live dB-grafiek genereert het dashboard gedempte HSL-kleuren op basis van de gulden hoek. Dit zorgt ervoor dat opeenvolgende indices maximaal verschillende tinten krijgen, zonder de verzadigde regenboog van een standaardcategorisch palet.

hue=(index×137.508)mod360sat=22+(indexmod5)×2light=46+((index×7)mod13)\begin{aligned} \mathrm{hue} &= (\mathrm{index} \times 137.508) \bmod 360 \\ \mathrm{sat} &= 22 + (\mathrm{index} \bmod 5) \times 2 \\ \mathrm{light} &= 46 + ((\mathrm{index} \times 7) \bmod 13) \end{aligned}
hue in graden, sat & light in procenten
#1
0.0°
#2
137.5°
#3
275.0°
#4
52.5°
#5
190.0°
Hue step ≈ 137.508° (golden angle). Consecutive indices land on maximally different hues, so adjacent meetpunten never share a colour.

De gulden hoek (137,508°) is de complementaire hoek van 360(11/φ)222,492°360 \cdot (1 - 1/\varphi) \approx 222{,}492° en verdeelt de kleurencirkel zo dat geen twee aangrenzende meetpunten een vergelijkbare tint krijgen.

Unit Conversions (Aviation → SI)

Het dashboard toont vliegtuiggegevens zowel in de oorspronkelijke luchtvaarteenheden (voet, knopen) als in SI-eenheden (meter, km/u) — leesbaar voor zowel professionals als omwonenden.

Voet → meter
altitude_m=altitude_ft×0.3048\mathrm{altitude\_m} = \mathrm{altitude\_ft} \times 0.3048
Knopen → km/u
speed_kmh=ground_speed_kt×1.852\mathrm{speed\_kmh} = \mathrm{ground\_speed\_kt} \times 1.852
Feet → Metres
35,000 ft = 10,668 m35,000 ft10,668 m× 0.3048
Knots → km/h
250 kt = 463 km/h250 kt463 km/h× 1.852

Waarden worden afgerond met Math.round() voor weergave en opgemaakt met .toLocaleString("nl-NL") voor de duizendtalscheiding.

16-Point Compass Rose

Vliegtuigkoersen (0–360°, 0° = noord) worden omgezet naar een Nederlandse 16-puntige kompasroos. De deling door 22,5° geeft een index in de lijst met kompaspunten.

index=round ⁣(degmod36022.5)mod16\mathrm{index} = \mathrm{round}\!\left(\frac{\deg \bmod 360}{22.5}\right) \bmod 16
22,5° = 360° / 16
16-point compass rose, every 22.5°NNNONOONOOOZOZOZZOZZZWZWWZWWWNWNWNNW
16 points × 22.5° = 360°. Dutch abbreviations: N = noord, O = oost, Z = zuid, W = west.

De kompaspunten zijn (Nederlands): N, NNO, NO, ONO, O, OZO, ZO, ZZO, Z, ZZW, ZW, WZW, W, WNW, NW, NNW. De 16-puntige verdeling is de luchtvaart- en meteorologische standaard.

ADS-B Altitude Bands

Vier hoogtebanden op de live ADS-B-kaart, gekleurd op relevantie voor geluidshinder. Vliegtuigen laag bij de grond (≤ 5.000 ft) zijn het meest relevant — klim- en cruise-fases steeds minder.

band(alt_ft)={groundalt_ft=nulllowalt_ft5,000midalt_ft20,000highotherwise\mathrm{band}(\mathrm{alt\_ft}) = \begin{cases} \mathrm{ground} & \mathrm{alt\_ft} = \texttt{null} \\ \mathrm{low} & \mathrm{alt\_ft} \le 5{,}000 \\ \mathrm{mid} & \mathrm{alt\_ft} \le 20{,}000 \\ \mathrm{high} & \mathrm{otherwise} \end{cases}
Retourneert de kleurnaam en hex-code
Altitude bands: ground, low, mid, highHIGH > 20,000 ftMID 5,000–20,000 ftLOW ≤ 5,000 ftGROUND (no alt)altitude_ft#f97316#10b981#3b82f6#9aa3a7
Lower bands (low, ground) are most relevant for noise impact — see §7.

De kleuren (ground: grijs, low: blauw, mid: groen, high: oranje) zijn gekozen voor leesbaarheid op de kaart, niet voor geluidsrelevantie.

PromQL Threshold Query & 80 dB Dispute Threshold

De 80 dB grens is een juridisch invariant — de wettelijke drempel voor het indienen van een geschil bij de Schiphol-geluidklachtenprocedure. Deze drempel is hard-coded in zowel de backfill als de live sampler en is niet configureerbaar via omgevingsvariabelen (in tegenstelling tot de dashboard-visuele drempels).

threshold=80.0dB\mathrm{threshold} = 80.0\,\text{dB}
DISPUTE_THRESHOLD_DB = 80.0 dB (hard-coded, juridisch invariant)

De backfill en de live sampler gebruiken dezelfde PromQL-query om ruismeetwaarden te vinden die deze drempel overschrijden. Sub-minuut pieken worden samengevat tot één rij per (meetpunt, minuut) via max_over_time.

max_over_time(noise_measurement{meetpunt_id=~"[0-9]+"}[1m]) >= 80

Onderdelen:

  • noise_measurement — gauge uit de casper-scraper (huidige dB per meetpunt).
  • {meetpunt_id=~"[0-9]+"} — labelfilter dat alleen numerieke meetpunt-ID's doorlaat (sluit legacy "meetpost X"-labels uit).
  • [1m] — sub-range: kijk 1 minuut terug vanaf elke querystap.
  • max_over_time(...) — kies de hoogste dB-waarde binnen het 1-minuut venster.
  • >= 80 — drempelfilter binnen Prom; waarden onder 80 worden NaN.

De live sampler (zie Sampler Window Overlap) gebruikt dezelfde query met een 90-seconden venster en een 60-seconden tick — de 30-seconden overlap garandeert dat geen enkele ≥80 dB meting tussen twee ticks valt.

Meetpunt Online Calculation

Per meetpunt worden tijdens elke data-refresh twee complementaire gauges uitgezonden. Het patroon scheidt "is de waarde betrouwbaar" van "wat is de waarde" — een meetpunt dat offline gaat toontnoise_measurement=0.0 maar noise.online=0, zodat het dashboard onderscheid kan maken tussen "het is stil op de terminal" en "de terminal is onbereikbaar".

noise.online{meetpunt_id,street,city}={1.0dB_is_valid0.0otherwisenoise.measurement{meetpunt_id,street,city}={float(dB)dB_is_valid0.0otherwise\begin{aligned} \mathrm{noise.online}\{\mathrm{meetpunt\_id, street, city}\} &= \begin{cases} 1.0 & \mathrm{dB\_is\_valid} \\ 0.0 & \mathrm{otherwise} \end{cases} \\ \mathrm{noise.measurement}\{\mathrm{meetpunt\_id, street, city}\} &= \begin{cases} \mathrm{float(dB)} & \mathrm{dB\_is\_valid} \\ 0.0 & \mathrm{otherwise} \end{cases} \end{aligned}
noise.online is binair (0.0 of 1.0), noise.measurement is dB
noise.online gauge (1.0 = valid)1.0noise.online
Valid reading
noise.measurement gauge (dB value)70noise.measurement
dB value
The two gauges are independent: online = 0 with measurement = 0.0 means the terminal is unreachable, not that it went silent.

Het noise.online-label laat het dashboard filteren opnoise.online == 1 om alleen betrouwbare metingen te tonen. PromQL's absent_over_time(noise_measurement[5m]) detecteert scraper-stilte ongeacht de huidige waarde.

Sampler Window Overlap

De live sampler tikt elke 60 seconden, maar bevraagt de laatste 90 seconden aan Prom-data. De 30-seconden overlap garandeert dat een ≥80 dB meting die precies op de grens tussen twee ticks valt nooit wordt gemist.

overlap=query_windowtick_interval=9060=30s\mathrm{overlap} = \mathrm{query\_window} - \mathrm{tick\_interval} = 90 - 60 = 30\,\mathrm{s}
_WINDOW_SECONDS = 90, sampler_interval_seconds = 60
Tick 1: [t-90, t] Tick 2: [t-30, t+60] overlap = 30st-90t-60t-30tt+30Tick 1[t-90, t] 90s windowTick 2[t-30, t+60]30s overlapsample at t-31 covered by Tick 2
tick_interval = 60s · query_window = 90s · overlap = 30s

Een sample op t-31 wordt nog gedekt door Tick 2 (omdat t31t30t-31 \ge t-30 in het overlap-gebied valt). De unieke constraint ON CONFLICT (meetpunt_id, event_ts) DO NOTHING in de database ontdubbelt eventueel per ongeluk dubbel ingevoegde rijen.

Slant Distance Correction

De standaard Haversine-afstand projecteert het vliegtuig op de grond (zee-niveau). In werkelijkheid bevindt het vliegtuig zich op hoogte — een vliegtuig dat 6 km horizontaal en 3 km boven de grond staat, heeft een werkelijke 3D-schuine afstand van ~6,7 km. De slant-distance correctie voegt de hoogteterm toe voor een nauwkeuriger afstandsmaat in de matching.

dslant=dhav2+(altft×0.30481000)2d_{\mathrm{slant}} = \sqrt{d_{\mathrm{hav}}^2 + \left(\frac{\mathrm{alt}_{\mathrm{ft}} \times 0.3048}{1000}\right)^2}
d_hav = Haversine afstand in km, alt_ft = vliegtuighoogte in voet

Vereenvoudigd met tussenstap:

altm=altft×0.3048\mathrm{alt}_{\mathrm{m}} = \mathrm{alt}_{\mathrm{ft}} \times 0.3048
alt_m = alt_ft × 0.3048 (conversie voet → meter)
dslant=dhav2+(altm1000)2d_{\mathrm{slant}} = \sqrt{d_{\mathrm{hav}}^2 + \left(\frac{\mathrm{alt}_{\mathrm{m}}}{1000}\right)^2}
d_slant = sqrt(d_hav² + (alt_m / 1000)²)
Slant distance correction: right triangle of ground distance and altituded_hav (ground distance)alt_m / 1000 (km)d_slantmeetpuntaircraftd_slant = sqrt(d_hav² + (alt_m / 1000)²)
A 10,000 ft aircraft 6 km away by ground projects a slant distance of ~7.1 km.

Wanneer alt_ft None is (geen hoogtesample binnen het 2-seconden merge-venster), retourneert de functie de zuivere Haversine-afstand — een bewuste "doe geen kwaad" fallback: niet elke transponder rapporteert barometrische hoogte, en het overslaan van de match is erger dan een grondgeprojecteerde afstand.

SymboolBronBeschrijving
d_slantuitvoerSchuine 3D-afstand in km, opgeslagen als aircraft_dist_km.
d_havHaversine-formuleGrootcirkelafstand in km, grondgeprojecteerd.
alt_ftdump1090_aircraft_altitude_ft (Prometheus)Barometrische hoogte in voet uit ADS-B transponder.
alt_malt_ft × 0.3048Hoogte in meters (SI-conversie).
0.3048constante FEET_PER_METREConversiefactor voet → meter.

Normalised Correlation Score

De score die de beste vliegtuig-match kiest voor een noise-event. In plaats van tijd en afstand gelijk te wegen (oude formule:score=Δt+d\mathrm{score} = |\Delta t| + d), normaliseert de nieuwe formule de afstand door een referentiesnelheid zodat "1 seconde er naast" equivalent is aan "1 kilometer er naast". Dit voorkomt dat verre vliegtuigen onterecht worden gekozen omdat ze toevallig dichtbij in de tijd zijn.

score=Δt+dslantvref\mathrm{score} = |\Delta t| + \frac{d_{\mathrm{slant}}}{v_{\mathrm{ref}}}
v_ref = 0.077 km/s ≈ 278 km/h (typische EHAM naderingssnelheid)
Correlation score: |Δt| + d_slant / v_ref060120180PH-NXI|Δt|d/v66.9 ✓G-EZAA|Δt|170.3score = |Δt| + d_slant / v_ref (lower is better)|Δt|d_slant / v_ref
PH-NXI: |Δt|=2s, d_slant=5.0km → 2 + 5.0/0.077 = 66.9 (winner)
G-EZAA: |Δt|=8s, d_slant=12.5km → 8 + 12.5/0.077 = 170.3

De referentiesnelheid vrefv_{\mathrm{ref}} is 0,077 km/s (77,2 m/s) — dit is de typische naderingssnelheid van een narrow-body naar EHAM baan 18R/36L, ontleend aan torenmetingen. Deze normalisatie betekent dat een kandidaat die 1 seconde afwijkt in tijd en 1 km in afstand een score krijgt van 1+1/0.077141 + 1/0.077 \approx 14 — hetzelfde als een kandidaat die 14 seconden afwijkt en 0 km. Hoe lager de score, hoe beter de match.

SymboolBronBeschrijving
scoreuitvoerGenormaliseerde correlatiescore — lager is beter. Alleen voor matching gebruikt, niet opgeslagen.
Δt|ts_aircraft − ts_event|Absolute tijdsverschil in seconden.
d_slantslant_distance()Schuine 3D-afstand in km.
v_refREFERENCE_VELOCITY_KM_PER_S0,077 km/s ≈ 278 km/h — referentiesnelheid voor tijds-afstand normalisatie.

Lden (Day-Evening-Night Level) — EUIND

De European Environmental Noise Directive (END, 2002/49/EC) definieert Lden als de 24-uurs A-gewogen gemiddelde geluidsbelasting met strafpunten voor avond- en nachtperiodes. Dit is de indicator die Nederlandse gemeenten rond Schiphol gebruiken voor jaarverslaglegging. LuchtTerreur berekent Lden per meetpunt over de vensters 24u, 7d en 30d.

Lden=10×log10[12×10Lday/10+4×10(Leve+5)/10+8×10(Lnight+10)/1024]\mathrm{Lden} = 10 \times \log_{10}\left[\frac{12 \times 10^{L_{\mathrm{day}}/10} + 4 \times 10^{(L_{\mathrm{eve}}+5)/10} + 8 \times 10^{(L_{\mathrm{night}}+10)/10}}{24}\right]
Lden — END Annex I, met +5 dB avond- en +10 dB nachtstraf
Lden: 12h day + 4h evening (+5dB) + 8h night (+10dB) over 24h00:0007:0019:0023:0024:00DAY · 12h · +0 dBEVE · 4h · +5 dBNIGHT · 8h · +10 dBL_dayL_eve + 5L_night + 10Lden = 10 · log₁₀[ (12·10^(L_day/10) + 4·10^((L_eve+5)/10) + 8·10^((L_night+10)/10) ) / 24 ]European Environmental Noise Directive (END, 2002/49/EC) Annex I
Time-of-day windows: day 07:00–19:00 (12h), evening 19:00–23:00 (4h), night 23:00–07:00 (8h).

Tijdvakken (lokale tijd, Europe/Amsterdam):

  • Day — 07:00–19:00 (12 uur). Geen straf. LdayL_{\mathrm{day}} is het energetisch gemiddelde over de dag.
  • Evening — 19:00–23:00 (4 uur). +5 dB straf. Leve+5L_{\mathrm{eve}} + 5 wordt in de energieterm gestopt.
  • Night — 23:00–07:00 (8 uur). +10 dB straf. Lnight+10L_{\mathrm{night}} + 10 wordt in de energieterm gestopt.

De straftermen worden binnen de logaritme toegevoegd (dus in het energiedomein), niet erna — dit is de correcte interpretatie van de END-standaard: een luide nachtelijke gebeurtenis weegt zwaarder dan een stille.

Belangrijke kanttekening: Lden voor nalevingsrapportage wordt berekend uit alle geluidsgebeurtenissen, niet alleen de ≥80 dB geschillen. De LuchtTerreur MVP gebruikt uitsluitend de ≥80 dB events uit de dispute_events-tabel als input. Wanneer het schema in de toekomst wordt uitgebreid met niet-geschil gebeurtenissen, pikt dezelfde code deze automatisch op.

SymboolBronBeschrijving
L_denuitvoer24-uurs dag-avond-nacht geluidsindicator in dB. Null als er geen events zijn.
L_day_partial_sum_db(day_vals)Energetisch gemiddelde over de dag: 07:00–19:00.
L_eve_partial_sum_db(eve_vals)Energetisch gemiddelde over de avond: 19:00–23:00.
L_night_partial_sum_db(night_vals)Energetisch gemiddelde over de nacht: 23:00–07:00.
+5 dBEND §A.1.1Avondstraf: 5 dB toegevoegd aan elk avond-event in het energiedomein.
+10 dBEND §A.1.1Nachtstraf: 10 dB toegevoegd aan elk nacht-event in het energiedomein.

Lnight (Night-Time Level)

De nachtelijke geluidsindicator Lnight is het energetisch gemiddelde over de 8-uurs nachtperiode (23:00–07:00 lokale tijd), zoals gedefinieerd in END 2002/49/EC Annex I. De WHO identificeert Lnight als de indicator die het sterkst correleert met slaapverstoring — voor omwonenden van Schiphol is dit vaak de meest relevante meetwaarde.

Lnight=10×log10[18×inight10Li/10]\mathrm{Lnight} = 10 \times \log_{10}\left[\frac{1}{8} \times \sum_{i \in \mathrm{night}} 10^{L_i/10}\right]
Lnight — gemiddeld over 8-uurs nachtvenster (23:00–07:00)
Lnight = 10 · log₁₀((1/8) · Σ 10^(L_i/10))707376798270737623:0000:0002:0004:0007:008-hour night windowLnight = 10 · log₁₀[ (1/8) · Σ 10^(L_i/10) ]L_i = individual noise event dB values between 23:00 and 07:00END 2002/49/EC · WHO sleep-disturbance indicator
Only events in the 23:00–07:00 window contribute; normalised by 8 hours.

Enkel events die in het 8-uurs nachtvenster vallen (23:00–07:00, lokale tijd) dragen bij aan Lnight. Het venster is 8 uur lang — de normalisatie deelt de som van de geluidsenergie door 8, niet door het aantal events. Dit betekent dat een meetpunt met 1 luide nachtelijke gebeurtenis een hogere Lnight krijgt dan een meetpunt met 10 stille gebeurtenissen, wat het fysiologische effect weerspiegelt.

Wanneer er geen events in het nachtvenster vallen, retourneert de functie None — een stille nacht is niet hetzelfde als 0 dB (de Lnight-formule is ongedefinieerd bij 0 events).

SymboolBronBeschrijving
Lnightuitvoer8-uurs nachtelijke geluidsindicator in dB. Null als er geen nacht-events zijn.
L_idispute_events.dBIndividuele geluidsgebeurtenis in dB, alleen events tussen 23:00–07:00.
1/88-uurs normalisatieHet nachtvenster is 8 uur. De som van de geluidsenergie wordt gedeeld door 8, niet door het aantal events.

Gezondheidsstatusclassificatie (WHO 2018 / RIVM)

De Gezondheidsstatusclassificatie is een 3-bandsysteem dat de Lden- en Lnight-indicatoren vertaalt naar een burgerbegrijpelijke gezondheidsimpact. Het systeem is gebaseerd op de WHO Environmental Noise Guidelines for the European Region (2018) en het jaarlijkse geluid-en-gezondheidsrapport van het RIVM (Rijksinstituut voor Volksgezondheid en Milieu).

De WHO 2018-richtlijn stelt dat voor vliegtuiglawaai de gemiddelde blootstelling onder 45 dB Lden en 40 dB Lnight moet liggen om nadelige gezondheidseffecten te minimaliseren (hart- en vaatziekten, slaapverstoring, cognitieve stoornissen bij kinderen). Boven 50 dB Lden spreekt de WHO van "significante nadelige gezondheidseffecten".

WHO Lden limiet45dB(geen significante effecten)WHO Lnight limiet40dB(slaapbescherming)EU actieplanning Lden50dB(ernstige effecten verwacht)\begin{aligned} \text{WHO Lden limiet} &\le 45 \,\text{dB} \quad \text{(geen significante effecten)} \\ \text{WHO Lnight limiet} &\le 40 \,\text{dB} \quad \text{(slaapbescherming)} \\ \text{EU actieplanning Lden} &\ge 50 \,\text{dB} \quad \text{(ernstige effecten verwacht)} \end{aligned}
WHO 2018 — referentielimieten voor vliegtuiglawaai
Health Bands: healthy (green) / warning (amber) / critical (red) — WHO 2018LdenLnightGezondWaarschuwingKritiek45 dBWHO-limiet50 dBEU actieplanningGezondKritiek40 dBWHO-limiet beide304045506070Gezond (geen effecten verwacht)Waarschuwing (effecten waargenomen)Kritiek (ernstige effecten verwacht)
WHO 2018 referentielimieten: Lden < 45 dB (gezond), Lden ≥ 45 dB (waarschuwing), Lden ≥ 50 dB (kritiek). Lnight: < 40 dB (gezond), ≥ 40 dB (kritiek — geen waarschuwingsband omdat de gezonde en kritieke drempel samenvallen).

Het systeem kent drie bands:

  • Gezond— Lden < 45 dB, Lnight < 40 dB. Geen significante gezondheidseffecten verwacht. Dit is de WHO 2018 aanbevolen limiet. Het dashboard toont een groene badge.
  • Waarschuwing — Lden 45–49 dB, of Lnight ≥ 40 dB terwijl Lden onder 50 blijft. Enige gezondheidseffecten worden waargenomen in de blootgestelde populatie. Het dashboard toont een oranje badge.
  • Kritiek — Lden ≥ 50 dB of Lnight ≥ 40 dB (de Lnight-kritiekgrens is gelijk aan de waarschuwingsgrens; als Lnight ≥ 40 dB, is de status altijd minstens "kritiek" voor Lnight). Significante nadelige gezondheidseffecten worden verwacht. Dit is ook de EU END-actieplanningsdrempel. Het dashboard toont een rode badge.

Belangrijke nuance: Lnight heeft geen aparte waarschuwingsband. De gezonde limiet (40 dB) en de kritieke drempel (40 dB) vallen samen volgens de WHO-richtlijn — elke overschrijding van 40 dB Lnight wordt als kritiek beschouwd voor slaapverstoring. De algehele status is altijd de slechtste van Lden en Lnight (zie de Classificatieformule hieronder).

SymboolBronBeschrijving
HEALTHY_LDEN_LIMIT_DBWHO 201845.0 dB — Lden drempel waaronder geen significante gezondheidseffecten worden verwacht.
HEALTHY_LNIGHT_LIMIT_DBWHO 201840.0 dB — Lnight drempel voor slaapbescherming; indoor-equivalent.
CRITICAL_LDEN_LIMIT_DBWHO 2018 / EU END50.0 dB — Lden drempel waarboven significante nadelige effecten worden verwacht.
CRITICAL_LNIGHT_LIMIT_DBWHO 201840.0 dB — Lnight drempel waarboven ernstige slaapverstoring wordt verwacht.

Classificatieformule — Worst-case Health Status

De functie classify_health_status(lden, lnight) in de API bepaalt de gezondheidsstatus door eerst Lden en Lnight onafhankelijk te classificeren in een numerieke band (0 = gezond, 1 = waarschuwing, 2 = kritiek), en vervolgens de slechtste van de twee te retourneren. Dit garandeert dat een meetpunt dat op Lden "gezond" maar op Lnight "kritiek" is, niet ten onrechte een groene badge krijgt.

band(x)={0x<gezond_limiet1gezond_limietx<kritiek_limiet2xkritiek_limiet\mathrm{band}(x) = \begin{cases} 0 & x < \text{gezond\_limiet} \\ 1 & \text{gezond\_limiet} \le x < \text{kritiek\_limiet} \\ 2 & x \ge \text{kritiek\_limiet} \end{cases}
Numerieke bandtoewijzing per indicator, daarna worst-case selectie
health_status={’healthy’max(band(Lden),band(Lnight))=0’warning’max(band(Lden),band(Lnight))=1’critical’max(band(Lden),band(Lnight))=2\mathrm{health\_status} = \begin{cases} \text{'healthy'} & \max(\mathrm{band(Lden)}, \mathrm{band(Lnight)}) = 0 \\ \text{'warning'} & \max(\mathrm{band(Lden)}, \mathrm{band(Lnight)}) = 1 \\ \text{'critical'} & \max(\mathrm{band(Lden)}, \mathrm{band(Lnight)}) = 2 \end{cases}
Worst-case: max(lden_band, lnight_band) → {healthy, warning, critical}
Worst-case health status: max(classify(Lden), classify(Lnight))LdenLnightLden ≥ 50?Lden ≥ 45?Lnight ≥ 40?gezondwaarschuwingkritiekmax(lden_band, lnight_band) → worst-case
De ene indicator kan 'gezond' zijn terwijl de andere 'kritiek' is — de eindstatus is de slechtste van beide. Als Lden = 48 dB (waarschuwing) en Lnight = 45 dB (kritiek), dan toont het dashboard 'kritiek'.

Uitgewerkt per indicator:

lden_band={0Lden<45.0(gezond)145.0Lden<50.0(waarschuwing)2Lden50.0(kritiek)\mathrm{lden\_band} = \begin{cases} 0 & \mathrm{Lden} < 45.0 \, (\text{gezond}) \\ 1 & 45.0 \le \mathrm{Lden} < 50.0 \, (\text{waarschuwing}) \\ 2 & \mathrm{Lden} \ge 50.0 \, (\text{kritiek}) \end{cases}
Lden-classificatie — band 0, 1, of 2
lnight_band={0Lnight<40.0(gezond)2Lnight40.0(kritiek)\mathrm{lnight\_band} = \begin{cases} 0 & \mathrm{Lnight} < 40.0 \, (\text{gezond}) \\ 2 & \mathrm{Lnight} \ge 40.0 \, (\text{kritiek}) \end{cases}
Lnight-classificatie — band 0 of 2 (geen aparte waarschuwingsband)

In Python-implementatie (uit api/src/inro_api/api/noise_indicators.py):

def classify_health_status(lden, lnight):
    bands = []
    if lden is not None:
        if lden >= CRITICAL_LDEN_LIMIT_DB:      # 50.0
            bands.append(2)
        elif lden >= HEALTHY_LDEN_LIMIT_DB:      # 45.0
            bands.append(1)
        else:
            bands.append(0)
    if lnight is not None:
        if lnight >= CRITICAL_LNIGHT_LIMIT_DB:   # 40.0
            bands.append(2)
        elif lnight >= HEALTHY_LNIGHT_LIMIT_DB:  # 40.0
            bands.append(1)  # same as critical for Lnight
        else:
            bands.append(0)
    if not bands:
        return None  # both inputs None -> no data
    worst = max(bands)
    return ("healthy", "warning", "critical")[worst]

De Python-functie retourneert None wanneer beide inputs None zijn (geen data — het dashboard toont "—" in plaats van een gezondheidsbadge). Dit voorkomt dat een stil meetpunt ten onrechte als "gezond" wordt gelabeld.

Een aparte functie exceeds_healthy_threshold() geeft een enkele boolean terug: True als Lden ≥ 45 dB of Lnight ≥ 40 dB. Het dashboard gebruikt deze om de "overschrijdt WHO-limiet" markering te tonen.

def exceeds_healthy_threshold(lden, lnight):
    if lden is not None and lden >= HEALTHY_LDEN_LIMIT_DB:
        return True
    if lnight is not None and lnight >= HEALTHY_LNIGHT_LIMIT_DB:
        return True
    return False
SymboolBronBeschrijving
lden_bandclassify_health_statusNumerieke band voor Lden: 0 (gezond), 1 (waarschuwing), 2 (kritiek).
lnight_bandclassify_health_statusNumerieke band voor Lnight: 0 (gezond), 2 (kritiek — Lnight heeft band 1 niet).
worstmax(lden_band, lnight_band)De slechtste van de twee bands — bepaalt de eindstatus.
outputtuple-index lookupEindstatus: 'healthy', 'warning', of 'critical'. None als beide inputs None zijn.

Monitoring Status — Vroege Waarschuwing (65 dB)

Naast de WHO-gezondheidsclassificatie heeft het dashboard een monitoringssysteem voor vroege waarschuwing dat verslechterende geluidsomstandigheden detecteert voordat gebeurtenissen de 80 dB geschildrempel bereiken. Dit is eenoperationele indicator, geen gezondheidslimiet — het helpt omwonenden proactief maatregelen te nemen (isolatie, gemeenschapsorganisatie) voordat de situatie escaleert.

MONITORING_WARNING_LDEN_DB=65.0MONITORING_WARNING_LNIGHT_DB=65.0\begin{aligned} \text{MONITORING\_WARNING\_LDEN\_DB} &= 65.0 \\ \text{MONITORING\_WARNING\_LNIGHT\_DB} &= 65.0 \end{aligned}
Monitoring drempelwaarden — 65 dB voor zowel Lden als Lnight
Monitoring bands: normal (<65 dB) / warning (≥65 dB) — vroege waarschuwing30 dB40 dB45 dB50 dB60 dB65 dB70 dB80 dB90 dBNORMaalWaarschuwing65 dBVroege waarschuwingWHO 45 dB80 dB geschil↑ waarschuwingszone
Monitoring banden: normaal (<65 dB) en waarschuwing (≥65 dB). De 65 dB drempel fungeert als vroege waarschuwing — 20 dB onder de geschildrempel van 80 dB, maar al 15 dB boven de WHO-gezondheidslimiet van 45 dB Lden.

De 65 dB drempel is gekozen om drie redenen:

  • ~20 dB onder de geschildrempel: 65 dB is 15 dB onder 80 dB. Dit geeft bewoners vroegtijdige signalering — weken of maanden voordat het geschilniveau wordt bereikt.
  • 15 dB boven de WHO-gezondheidslimiet: Een stijging van 50 dB naar 65 dB betekent een verslechtering van 15 dB in geluidsbelasting, wat wijst op een duidelijke toename van vliegverkeer of gewijzigde vliegroutes.
  • Praktisch vroegsignaal: Bij 65 dB Lden/Lnight is de kans groot dat piekgebeurtenissen (boven 80 dB) op korte termijn zullen toenemen, tenzij er beleidsingrepen plaatsvinden.
monitoring_status(x)={’normal’x<65.0’warning’x65.0\mathrm{monitoring\_status}(x) = \begin{cases} \text{'normal'} & x < 65.0 \\ \text{'warning'} & x \ge 65.0 \end{cases}
Monitoringsclassificatie — apart voor Lden en Lnight

In Python:

def classify_monitoring_status(lden, lnight):
    lden_status = None
    if lden is not None:
        lden_status = "warning" if lden >= 65.0 else "normal"
    lnight_status = None
    if lnight is not None:
        lnight_status = "warning" if lnight >= 65.0 else "normal"
    return lden_status, lnight_status

def exceeds_monitoring_warning(lden, lnight):
    lden_status, lnight_status = classify_monitoring_status(lden, lnight)
    return lden_status == "warning" or lnight_status == "warning"

Het dashboard toont de monitoringsstatus als een aparte indicator naast de WHO-gezondheidsbadge. Een meetpunt kan bijvoorbeeld WHO "gezond" zijn (Lden = 42 dB) maar monitoring "waarschuwing" als de nachtpieken richting 65 dB gaan — een vroege indicatie dat de situatie aan het verslechteren is.

SymboolBronBeschrijving
MONITORING_WARNING_LDEN_DBconstante65.0 dB — Lden drempel voor vroege waarschuwing.
MONITORING_WARNING_LNIGHT_DBconstante65.0 dB — Lnight drempel voor vroege waarschuwing.
monitoring_ldenclassify_monitoring_status()Lden monitoringsstatus: 'normal' of 'warning' (None bij geen data).
monitoring_lnightclassify_monitoring_status()Lnight monitoringsstatus: 'normal' of 'warning' (None bij geen data).
exceeds_monitoring_warningexceeds_monitoring_warning()Boolean. True als Lden ≥ 65 dB of Lnight ≥ 65 dB.

Praktijkvoorbeelden — Gezondheids- en Monitoringsclassificatie

Hieronder staan vier voorbeelden die tonen hoe de classificatie in de praktijk werkt. De voorbeelden gebruiken realistische Lden/Lnight-waarden voor meetpunt 2 (Zwanenburg / Olmenlaan).

ScenarioLdenLnightGezondheidsstatusMonitoring
Rustige dag38.232.5GezondNormaal
Matig druk46.838.1WaarschuwingNormaal
Zeer druk52.341.7KritiekWaarschuwing
Nachtelijke pieken42.143.2KritiekNormaal

Scenario 1: Rustige dag — Lden 38,2 dB, Lnight 32,5 dB. Beide liggen ruim onder de WHO-limieten. Het dashboard toont een groene "Gezond"-badge. De monitoringsstatus is "Normaal" (ver onder 65 dB).

Scenario 2: Matig druk — Lden 46,8 dB, Lnight 38,1 dB. Lden overschrijdt de 45 dB WHO-limiet, maar blijft onder 50 dB (dus "Waarschuwing"). Lnight is gezond. De worst-case status is "Waarschuwing". Monitoringstatus is "Normaal" (beide onder 65 dB).

Scenario 3: Zeer druk — Lden 52,3 dB, Lnight 41,7 dB. Beide overschrijden de kritieke drempels. De worst-case status is "Kritiek". De Lden van 52,3 dB zit nog onder de monitoring-waarschuwing van 65 dB, maar de stijging van 46,8 naar 52,3 dB is al een signaal van verslechtering.

Scenario 4: Nachtelijke pieken — Lden 42,1 dB (gezond), maar Lnight 43,2 dB (kritiek). Dit toont de kracht van de worst-case-regel: een meetpunt dat overdag stil is maar 's nachts luide gebeurtenissen heeft, krijgt een rode "Kritiek"-badge. De Lden alleen zou ten onrechte een groene badge geven.

Voorbeeld van de JSON-respons van de API (endpoint /noise-indicators/2 — het meetpunt ID kan worden aangepast):

{
   "meetpunt_id": "2",
  "windows": {
    "24h": {
      "lden": 52.3,
      "lnight": 41.7,
      "event_count": 47,
      "health_status": "critical",
      "healthy_limit": 45.0,
      "lnight_healthy_limit": 40.0,
      "exceeds_health": true,
      "monitoring_lden": "normal",
      "monitoring_lnight": "normal",
      "exceeds_monitoring_warning": false,
      "health_threshold_lden": 45.0,
      "health_threshold_lnight": 40.0,
      "exceeds_health_limits": true,
      "monitoring_warning_lden": 65.0,
      "monitoring_warning_lnight": 65.0
    }
  },
  "data_note": "Lden/Lnight computed from ≥80 dB events only..."
}

Het dashboard gebruikt health_status voor de kleurgecodeerde badge, exceeds_health_limits voor de "overschrijdt WHO-limiet"-markering en exceeds_monitoring_warning voor de vroege waarschuwing. De health_threshold_* velden worden per response teruggestuurd zodat de frontend niet hoeft te raden naar de drempelwaarden — zelfs niet nadat de API is bijgewerkt.