JPXWatch Noise Estimation Pipeline — Methodology Audit
Audit date: 2026-04-12
Codebase: main branch at commit 5ca7c6c
Auditor: Claude (automated code audit)
Status: Complete
Purpose. This document is a factual, read-only accounting of what the JPXWatch noise estimation code does today. It is not a methodology document for the public, not a defense of the methodology, and not a list of recommended improvements. Every claim cites a file path and line number so it can be independently verified.
Executive Summary
Discrepancies between code locations computing the same thing
-
Two source-level lookup tables with different coverage.
getNoiseDb()(lib/noise/getNoiseDb.ts) falls back togetAircraftNoiseProfile()(28 types indata/noise/aircraftNoiseProfiles.ts), whiletrackNoiseCalculator.tsand related modules callgetEASANoiseProfile()(47 types indata/noise/easa/icaoToEasaMap.ts). An aircraft type present in the EASA map but absent from the client-side table receives different dB values depending on which code path evaluates it. See §1b. -
Inconsistent ground elevation for MSL→AGL conversion. Trail noise uses
GROUND_ELEVATION_FT = 30ft (trailNoise.ts:22); footprint calculator usesKJPX_ELEVATION_FT = 55ft (footprintCalculator.ts:36). The same aircraft at the same MSL altitude produces different AGL values — and thus different noise estimates — in trails vs. footprints. See §3c. -
Duplicate aircraft classification logic.
lib/flightaware/classify.tsandlib/webhooks/processor.tseach define their ownHELICOPTER_TYPES,JET_TYPES, andFIXED_WING_TYPESsets (copy-pasted, not shared). The webhook processor omits seaplane detection. See §2f. -
Noise category boundaries differ between classification systems.
getNoiseCategory()uses 75/82/88 dB boundaries;getDbSeverityLevel()uses 55/70/85;getDbColor()uses 55/65/75/85;getNoiseColorFromDb()uses 65/75/85. These serve different purposes but this is not documented. See §1f. -
SEL formula omits atmospheric absorption. The DNL calculator's
calculateSEL()uses only geometric spreading, whilecalculateGroundNoise()in the track calculator includes both geometric spreading and atmospheric absorption (0.5 dB/1000 ft). See §4b vs §3b. -
Two different "nighttime" definitions. DNL nighttime penalty: 10 PM – 7 AM (FAA standard). Town curfew / Noise Impact Score: 9 PM – 7 AM. This is correctly documented in the code (
dnlCalculator.ts:21–24). See §4c.
Unattributed magic numbers requiring sourcing
| Constant | Value | Location | Issue |
|---|---|---|---|
| FAA approach dB offset | +2 dB | icaoToEasaMap.ts:682,690 | Applied to FAA-measured lamax1000ft to estimate approach dB. No citation. |
| Default flyover exposure | 15 seconds | dnlCalculator.ts:39 | Used in SEL calculation. About page calls it "standard" but cites no source. |
| Atmospheric absorption | 0.5 dB/1000 ft | Multiple files | Comment references ISO 9613-1 but 0.5 is a simplified single-number A-weighted average, not the full frequency-dependent model. |
| Trail ground elevation | 30 ft | trailNoise.ts:22 | Comment says "East End average" but no DEM or survey source cited. |
| Lateral attenuation table | 0–10 dB over 0–90° | trackNoiseCalculator.ts:185–196 | Labeled "SAE-AIR-5662 simplified" — the simplification method is not described. |
| Category average dB values | heli=84, jet=88, fw=76, unk=80 | icaoToEasaMap.ts:37–42 | No citation for how these averages were computed. |
| Weather adjustment values | wind ±2–6 dB, inversion 0–8 dB | weatherAdjustments.ts:65–82 | Comment references ISO 9613-2 but specific values are not directly from the standard. |
| Noise Impact Score weights | 40/30/30% | noiseImpactScore.ts:111 | Custom metric. No external basis cited. |
| Noise Impact Score scale factors | 500, 2, 1000 | noiseImpactScore.ts:40,55,75 | Chosen so that 20% loud ops, 50 ops/day, or 10% curfew ops each produce a score of 100. No cited basis for these anchoring points. |
Inconsistencies with the About page
-
Elevation source. The About page (
about/page.tsx:1172–1174) claims "For properties where verified elevation data is available… we use the actual ground elevation from the USGS National Elevation Dataset" and "for all other locations, we use the airport field elevation of 55 feet MSL." The trail noise code uses a hardcoded 30 ft (trailNoise.ts:22). Neither 30 nor 55 corresponds to USGS DEM integration — no USGS data fetch was found in the codebase. -
Weather effects. The About page (
about/page.tsx:1381) states "the model uses standard atmospheric conditions." The code includes a full weather adjustment module (weatherAdjustments.ts) that is plumbed intocalculateGroundNoise()as an optional parameter but is not connected to any live weather feed. The weather model exists but is dormant. -
"47 aircraft types." The About page and
docs/NOISE-METHODOLOGY.mdreference 47 aircraft types with EASA certification data. This matches the EASA map (icaoToEasaMap.ts:45–657). However, the trail visualization andgetNoiseDb()fallback use the 28-type client-side table. The number "47" is accurate for the EASA map but not for all code paths.
Open TODOs in the noise pipeline
lib/calculations/noiseImpactScore.ts:149: "Add aloud_jetscolumn to monthly aggregates to improve accuracy." Currently all jets are treated as loud (≥85 dB) in aggregate calculations, overestimating the fleet mix score.
Table of Contents
- Aircraft Source Levels
- Aircraft Classification
- Propagation Model
- DNL Computation
- Noise Rank
- Noise Trails
- High-Noise Events
1. Aircraft Source Levels
1a. What the code does
JPXWatch assigns every flight a point-source noise level in dB (LAmax at 1,000 ft reference distance) for both takeoff and approach phases. These values are the starting point for all downstream noise calculations (propagation, DNL, trails, high-noise events).
There are three independent data stores for source levels, queried through a priority cascade:
-
aircraftNoiseProfiles— a hand-curated lookup table of 28 aircraft types, used as the client-side fallback.data/noise/aircraftNoiseProfiles.ts:5–216 -
icaoToEasaMap— a larger auto-generated map of 47 ICAO types with EASA certification EPNdB values converted to estimated LAmax.data/noise/easa/icaoToEasaMap.ts:45–657 -
faaHelicopterMeasurements— FAA ROSAP field-measurement database with 20 helicopter entries (multiple variants per type), providing measured LAmax at 1,000 ft.data/noise/faa/helicopterMeasurements.ts:48–348
1b. Lookup cascade
The primary lookup function is getEASANoiseProfile(icaoType) (data/noise/easa/icaoToEasaMap.ts:664–716):
- Normalize type code to uppercase (line 665).
- Check
hasFAAMeasurement()— if FAA ROSAP data exists for this type, use itslamax1000ftastakeoffDbandlamax1000ft + 2asapproachDb(lines 669–695). Data source set to'FAA_MEASURED', confidence'high'. - If no FAA data, check
icaoToEasaMapfor an EASA-derived profile (line 698). - If neither exists, return an unknown fallback:
takeoffDb = 80,approachDb = 76, data source'UNVERIFIED', confidence'low'(lines 703–715). The 80 comes fromCATEGORY_AVERAGES.unknown.default(line 711); the 76 is computed as80 - 4(line 712).
A separate function getNoiseDb(flight) (lib/noise/getNoiseDb.ts:34–42) selects the direction-appropriate value:
- Prefer
flight.noise_profile.effective_dbif present (line 36–37). - Else call
getAircraftNoiseProfile(flight.aircraft_type)— which queries the client-sideaircraftNoiseProfilesmap, not the EASA map — and returnsapproachDbfor arrivals,takeoffDbfor departures (lines 40–41).
FLAG — Discrepancy.
getNoiseDb()falls back togetAircraftNoiseProfile()(the 28-type client-side table), whiletrackNoiseCalculator.tsandfootprintCalculator.tsusegetEASANoiseProfile()(the 47-type EASA/FAA table) orgetAircraftNoiseProfile()respectively. A flight whose type is in the EASA map but not in the client-sideaircraftNoiseProfilestable will get different source levels depending on which code path evaluates it. See Section 3 for details.
1c. Client-side profile table (aircraftNoiseProfiles)
All values in dB LAmax at 1,000 ft. File: data/noise/aircraftNoiseProfiles.ts.
| ICAO | Category | takeoffDb | approachDb | noiseCategory | Lines |
|---|---|---|---|---|---|
| R22 | helicopter | 78 | 76 | moderate | 7–13 |
| R44 | helicopter | 82 | 80 | loud | 14–20 |
| R66 | helicopter | 83 | 81 | loud | 21–27 |
| S76 | helicopter | 88 | 85 | very_loud | 28–34 |
| EC35 | helicopter | 84 | 82 | loud | 35–41 |
| A109 | helicopter | 85 | 83 | loud | 42–48 |
| B06 | helicopter | 83 | 81 | loud | 49–55 |
| B407 | helicopter | 84 | 82 | loud | 56–62 |
| AS50 | helicopter | 82 | 80 | loud | 63–69 |
| GLF5 | jet | 92 | 88 | very_loud | 72–78 |
| GLF4 | jet | 90 | 86 | very_loud | 79–85 |
| GLEX | jet | 91 | 87 | very_loud | 86–92 |
| C56X | jet | 86 | 82 | loud | 93–99 |
| C680 | jet | 85 | 81 | loud | 100–106 |
| C525 | jet | 80 | 76 | moderate | 107–113 |
| E55P | jet | 82 | 78 | moderate | 114–120 |
| PC12 | fixed_wing | 78 | 75 | moderate | 121–127 |
| LJ45 | jet | 84 | 80 | loud | 128–134 |
| FA50 | jet | 85 | 81 | loud | 135–141 |
| C172 | fixed_wing | 75 | 72 | moderate | 144–150 |
| C182 | fixed_wing | 76 | 73 | moderate | 151–157 |
| C206 | fixed_wing | 77 | 74 | moderate | 158–164 |
| PA28 | fixed_wing | 74 | 71 | moderate | 165–171 |
| PA32 | fixed_wing | 76 | 73 | moderate | 172–178 |
| BE36 | fixed_wing | 77 | 74 | moderate | 179–185 |
| SR22 | fixed_wing | 76 | 73 | moderate | 186–192 |
| P28A | fixed_wing | 72 | 69 | quiet | 193–199 |
| C150 | fixed_wing | 70 | 67 | quiet | 200–206 |
| UNKN | unknown | 80 | 76 | moderate | 209–215 |
Provenance: The file comment (line 3) says "Based on FAA noise certification data and typical aircraft noise levels at 1000ft altitude during takeoff/approach." No individual per-type citation is given. The values are round numbers, not raw certification values, suggesting they are rounded estimates derived from certification data rather than direct copies.
Fallback: getAircraftNoiseProfile(type) returns the UNKN profile (takeoffDb=80, approachDb=76) for any type not in the table (line 219–220).
1d. EASA map (icaoToEasaMap)
File: data/noise/easa/icaoToEasaMap.ts. Auto-generated 2026-02-12 (line 2). Source: EASA Certification Noise Levels database (line 3). Updated with FAA ROSAP measurements (line 4).
The file header (lines 7–13) documents the EPNdB-to-LAmax conversion: "LAmax ≈ EPNdB - (7 to 13), varying by aircraft spectrum shape. For helicopters, the offset is typically 7–10 dB; for jets, 10–13 dB."
47 aircraft types are mapped. Each entry includes: lateralEpnl, flyoverEpnl, approachEpnl (nullable; raw EASA EPNdB values), takeoffDb, approachDb (converted LAmax), dataSource, and confidence.
Category averages for unknown types (data/noise/easa/icaoToEasaMap.ts:37–42):
| Category | Default | Light | Medium | Heavy |
|---|---|---|---|---|
| helicopter | 84 | 78 | 84 | 90 |
| jet | 88 | 82 | 88 | 94 |
| fixed_wing | 76 | 72 | 76 | 82 |
| unknown | 80 | — | — | — |
Provenance of category averages: Not individually cited in code. The header states they are "representative LAmax values derived from the same methodology" (line 13) as the per-type EPNdB-to-LAmax conversion.
1e. FAA ROSAP helicopter measurements
File: data/noise/faa/helicopterMeasurements.ts. Contains 20 measurement entries for helicopter types, each with fields: takeoffEpndb, approachEpndb, flyoverEpndb, sel1000ft, lamax1000ft, faaReport (citation), measurementYear.
Selected entries (complete set in the file):
| ICAO | Model | lamax1000ft | faaReport | Year | Lines |
|---|---|---|---|---|---|
| B06 | Bell 206B-3 | 82.0 | DOT/FAA/CT-84-2 | 1984 | 51–63 |
| B06 | Bell 206L-1 | 80.5 | DOT/FAA/CT-84-2 | 1984 | 65–75 |
| B407 | Bell 407 | 83.0 | FAA-AEE-01-04 | 2001 | 77–87 |
| S76 | Sikorsky S-76A | 84.0 | DOT/FAA/CT-84-2 | 1984 | 130–141 |
| S76 | S-76C++ | 82.5 | FAA-AEE-09-01 | 2009 | 156–167 |
| R22 | Robinson R22 | 75.5 | FAA-AEE-01-04 | 2001 | 294–305 |
| R44 | Robinson R44 | 77.5 | FAA-AEE-01-04 | 2001 | 307–318 |
| R66 | Robinson R66 | 78.8 | FAA-AEE-15-01 | 2015 | 320–331 |
| EC35 | EC135 | 80.5 | FAA-AEE-01-04 | 2001 | 253–264 |
| AS50 | AS350 | 81.5 | FAA-AEE-01-04 | 2001 | 240–251 |
| A109 | AW109SP | 82.0 | FAA-AEE-15-01 | 2015 | 211–222 |
| H60 | UH-60A | 87.5 | DOT/FAA/CT-84-2 | 1984 | 336–347 |
getFAAMeasurement() (line 356–372) returns the most recent measurement for a type (sorted by year descending).
Approach dB approximation: When FAA data is used, approachDb is set to lamax1000ft + 2 (lines 682, 690). The +2 dB offset is hardcoded with no comment explaining its provenance.
FLAG — Unattributed constant. The
+2 dBapproach-vs-takeoff offset applied to FAA-measuredlamax1000ftvalues (line 682, 690) has no citation or comment. The client-sideaircraftNoiseProfilestable uses per-type approach/takeoff differentials that range from 2–4 dB, suggesting this is a simplified approximation.
1f. Noise category thresholds
getNoiseCategory(db) in lib/noise/getNoiseDb.ts:47–52:
| Threshold | Category |
|---|---|
| ≥ 88 dB | very_loud |
| ≥ 82 dB | loud |
| ≥ 75 dB | moderate |
| < 75 dB | quiet |
These thresholds are used for display classification only; they do not affect noise calculations.
A separate severity scale exists in types/noise.ts:207–220:
| Threshold | Severity | Color |
|---|---|---|
| < 55 dB | low | #22c55e (green) |
| < 65 dB | — | #84cc16 (lime) |
| < 70 dB | moderate | — |
| < 75 dB | — | #eab308 (yellow) |
| < 85 dB | high | #f97316 (orange) |
| ≥ 85 dB | severe | #ef4444 (red) |
FLAG — Discrepancy. The
getNoiseCategory()boundaries (75/82/88) do not align with thegetDbSeverityLevel()boundaries (55/70/85) or thegetDbColor()boundaries (55/65/75/85). These appear to serve different purposes (aircraft-type classification vs. received-ground-level severity) but this is not documented in the code.
1g. TODOs and known issues
lib/calculations/noiseImpactScore.ts:145–149: TODO to add aloud_jetscolumn to monthly aggregates. Currently all jets are treated as ≥ 85 dB in aggregate calculations, overestimating the fleet mix score.- No TODOs or FIXMEs in the source-level data files themselves.
2. Aircraft Classification
2a. What the code does
JPXWatch classifies each flight into one of five categories: helicopter, jet, fixed_wing, seaplane, or unknown. Classification determines which source-level dB value is used and how the flight is counted in noise metrics (e.g., all helicopters are counted as "loud" in the Noise Index regardless of their dB value).
The primary input is the ICAO aircraft type code from FlightAware (e.g., S76, GLF5, C172). The system does not directly use ADS-B hex codes for classification — FlightAware resolves hex codes to type codes upstream.
2b. Primary classification function
classifyAircraft(icaoType) in lib/flightaware/classify.ts:199–217:
- If input is null/undefined →
'unknown'(line 201). - Trim and uppercase the code (line 204).
- Check against
HELICOPTER_TYPESSet (48 types, lines 22–48) →'helicopter'. - Check against
JET_TYPESSet (60 types, lines 53–81) →'jet'. - Check against
FIXED_WING_TYPESSet (68 types, lines 87–110) →'fixed_wing'. - No match →
'unknown'(line 216).
All type sets are hardcoded. There is no dynamic discovery of new aircraft types.
2c. Type set contents
Helicopters (48 types, classify.ts:22–48):
Robinson (R22, R44, R66), Airbus/Eurocopter (EC20, EC30, EC35, EC45, EC55, EC75, EC25, AS50, AS55, AS65, AS32, AS33, AS35, H125, H130, H135, H145, H155, H160, H175, H215, H225), Bell (B06, B06T, B204, B205, B206, B209, B212, B214, B222, B230, B407, B412, B427, B429, B430, B505, B525), Sikorsky (S76, S61, S70, S92, S58, S64, S76B, S76C, S76D, H60, S300), Leonardo (A109, A119, A139, A149, A169, A189, AW09, AW39, AW69, AW89), MD (MD52, MD60, EXPL, NOTR, H369, H500), Enstrom (EN28, EN48), Schweizer (S269, S333, H269), generic (HELI).
Jets (60 types, classify.ts:53–81):
Gulfstream (GLF2–GLF6, GLEX, G150–G800), Bombardier (CL30, CL35, CL60, BD70, GL5T–GL7T), Learjet (LJ23–LJ75), Cessna Citation (C500–C750), Dassault (FA10–FA8X, F900, F2TH), Embraer (E135–E550), plus PC24, HDJT, EA50, SF50, PRM1, H25A–H25C, AJET.
Fixed Wing (68 types, classify.ts:87–110):
Cessna piston/turboprop (C150–C441), Piper (P28A–PA60, PC12), Beechcraft (BE33–B300), Mooney (M20J–M20U), Cirrus (SR20, SR22), Diamond (DA20–DA62), TBM (TBM7–TBM9), de Havilland (DHC2, DHC3, DHC6).
2d. Seaplane detection
A multi-layer seaplane override exists in classify.ts:160–192 via isSeaplaneFlight(). It is not called from the cron or webhook ingestion pipelines — see section 2f below.
Layer 1 — Always-seaplane types (classify.ts:118–122):
DHC2 (de Havilland Beaver) and DHC3 (Otter) are always classified as seaplanes.
Layer 2 — Seaplane-candidate types (classify.ts:119):
C208, KODK, KOD1, DHC2, DHC3, DHC6 — these may be seaplanes depending on operator/route.
Layer 3 — Known seaplane operators (classify.ts:125–137):
- ICAO codes:
FTO(Tropic Ocean Airways),PGN(Tailwind Air). - Display names:
TROPIC OCEAN AIRWAYS,TAILWIND AIR,ACADIAN SEAPLANES,FLYTHEWHALE,FLY THE WHALE. - Special case:
MONTAUK SKY/MONTAUK SKY LLC(classify.ts:140–141) — classified as seaplane only if the route includes NYC Skyports; otherwisefixed_wing.
Layer 4 — Skyports detection (classify.ts:143–150):
Detects NYC seaplane base by coordinate prefix (L 40.73* / -73.97*) or FAA identifier (6N5, 6N7, KJRA, KJRB).
2e. Data sources feeding classification
| Source | Input field | Used for | File |
|---|---|---|---|
| FlightAware AeroAPI | aircraft_type (ICAO code) | Primary classification input | app/api/cron/daily-pull/route.ts |
| FlightAware AeroAPI | operator (ICAO code) | Seaplane operator check (unused in pipeline) | lib/flightaware/classify.ts |
| FlightAware AeroAPI | registration (N-number) | Not used for classification; used for FAA registry owner lookup only | lib/faa/registryDisplay.ts |
| FAA Aircraft Registry | n_number, aircraft_mfr, aircraft_model | Owner identity display; not used for category classification | app/api/cron/refresh-faa-registry/route.ts |
2f. Duplicate classification logic
FLAG — Code duplication. Aircraft classification is implemented independently in two files:
lib/flightaware/classify.ts:199–217— the canonical implementation, with seaplane detection.lib/webhooks/processor.ts:25–82— a duplicate, noted as "mirrors Python classify.py" (line 22).Both contain identical
HELICOPTER_TYPES,JET_TYPES,FIXED_WING_TYPESsets as of the audit date, but they are not shared — they are copy-pasted. The webhook processor'sclassifyAircraft()(line 75–82) does not callisSeaplaneFlight(), so webhook-ingested flights with seaplane-candidate types (e.g., C208 operated by Tropic Ocean Airways) will be classified asfixed_wingrather thanseaplane.
2g. How unknowns are handled
- Null/undefined
aircraft_type→'unknown'category. - Unrecognized ICAO code →
'unknown'category. - In noise calculations: unknown aircraft get the UNKN profile (takeoffDb=80, approachDb=76) from
aircraftNoiseProfiles.ts:209–215, orCATEGORY_AVERAGES.unknown.default(80) from the EASA map fallback. - In the Noise Index: unknown-category flights are not counted as loud operations — only
helicopterandjetcategories contribute (lib/noise/getNoiseDb.ts:60–73). - In fleet filtering: unknown flights are grouped with fixed-wing under the "other" filter (
lib/filterFlightsByFleet.ts:10).
2h. TODOs and known issues
- No TODOs or FIXMEs in the classification files.
- The seaplane detection system (
isSeaplaneFlight) exists but is not integrated into either ingestion pathway (cron or webhook). Its export fromlib/flightaware/index.tsmakes it available, but no caller was found in the ingestion code.
3. Propagation Model
3a. What the code does
The propagation model computes estimated ground-level noise in dB given a source level, aircraft position, and receiver position. There are four independent implementations of this calculation at different levels of fidelity:
- Full model —
calculateGroundNoise()inlib/noise/trackNoiseCalculator.ts:338–434. Includes geometric spreading, atmospheric absorption, lateral attenuation (SAE-AIR-5662), and optional weather adjustment. - Simplified footprint model —
estimateGroundDb()inlib/noise/footprintCalculator.ts:117–130. Geometric spreading + atmospheric absorption only. Omits lateral attenuation (noted in comment, lines 111–115). - Trail model —
estimateGroundNoise()inlib/noise/trailNoise.ts:60–71. Altitude-only (assumes listener directly below aircraft). Geometric spreading + atmospheric absorption. - Legacy simple estimate —
getSimpleNoiseEstimate()inlib/noise/trackNoiseCalculator.ts:634–661. Altitude-only, same physics as trail model.
3b. Full propagation formula
calculateGroundNoise() (trackNoiseCalculator.ts:338–434):
groundDb = sourceDb
− geometricAttenuation
− atmosphericAttenuation
− lateralAttenuation
+ weatherAdjustment
Step 1: Horizontal distance (line 361–363) Haversine formula between observer and aircraft lat/lon, result in feet.
Step 2: Slant distance (line 366)
slantDistanceFt = √(altitude_ft² + horizontalDistanceFt²)
Clamped to minimum 100 ft (line 369) to prevent extreme values.
Step 3: Geometric spreading (lines 372–375)
geometricAttenuation = 20 × log₁₀(effectiveSlantDistance / 1000)
Reference distance is CERTIFICATION_REFERENCE_DISTANCE_FT = 1000 ft (line 98).
This is the inverse square law: −6 dB per doubling of distance.
Step 4: Atmospheric absorption (lines 377–379)
atmosphericAttenuation = (effectiveSlantDistance / 1000) × 0.5
Coefficient is ATMOSPHERIC_ABSORPTION_COEFFICIENT = 0.5 dB per 1,000 ft (line 101).
Step 5: Lateral attenuation (lines 382–388)
Applied only if aircraft heading is known. Uses SAE-AIR-5662 simplified model.
Angle computed as bearing difference between aircraft heading and observer direction (calculateLateralAngle(), lines 286–306), clamped to 0–90°.
Attenuation looked up via linear interpolation from a 10-point table (LATERAL_ATTENUATION_TABLE, lines 185–196):
| Angle (°) | Attenuation (dB) | Line |
|---|---|---|
| 0 | 0.0 | 186 |
| 10 | 0.5 | 187 |
| 20 | 1.2 | 188 |
| 30 | 2.5 | 189 |
| 40 | 4.0 | 190 |
| 50 | 5.5 | 191 |
| 60 | 7.0 | 192 |
| 70 | 8.5 | 193 |
| 80 | 9.5 | 194 |
| 90 | 10.0 | 195 |
Maximum lateral attenuation: 10 dB at 90° (perpendicular to flight path).
Step 6: Weather adjustment (lines 390–412) Applied only if weather conditions are passed. See section 3d below. Positive values = louder.
Step 7: Floor (line 422)
Result clamped to minimum 0 dB (Math.max(0, roundedDb)).
All intermediate and final values rounded to 1 decimal place (0.1 dB precision).
3c. Propagation constants
| Constant | Value | Unit | File | Line | Provenance |
|---|---|---|---|---|---|
CERTIFICATION_REFERENCE_DISTANCE_FT | 1000 | ft | trackNoiseCalculator.ts | 98 | EASA certification standard (304.8 m) |
ATMOSPHERIC_ABSORPTION_COEFFICIENT | 0.5 | dB/1000 ft | trackNoiseCalculator.ts | 101 | Comment: "A-weighted average." Referenced to ISO 9613-1 in file header (line 9). |
EARTH_RADIUS_FT | 20,902,230.97 | ft | trackNoiseCalculator.ts | 104 | 6371 km × 3280.84 ft/km |
FT_PER_NM | 6076.12 | ft/nm | trackNoiseCalculator.ts | 107 | Standard conversion |
REFERENCE_DISTANCE_FT | 1000 | ft | footprintCalculator.ts | 33 | Same constant, separately defined |
ATMOSPHERIC_ABSORPTION_PER_1000FT | 0.5 | dB | footprintCalculator.ts | 34 | Same constant, separately defined |
EARTH_RADIUS_FT | 20,902,231 | ft | footprintCalculator.ts | 35 | Rounded differently (integer vs. .97) |
KJPX_ELEVATION_FT | 55 | ft MSL | footprintCalculator.ts | 36 | Airport field elevation |
GROUND_ELEVATION_FT | 30 | ft ASL | trailNoise.ts | 22 | Comment: "East End average ground elevation" |
REFERENCE_DISTANCE_FT | 1000 | ft | trailNoise.ts | 25 | Same constant, third definition |
REFERENCE_DISTANCE_FT | 1000 | ft | dnlCalculator.ts | 42 | Same constant, fourth definition |
FLAG — Inconsistent ground elevation. The footprint calculator uses
KJPX_ELEVATION_FT = 55ft (field elevation) for MSL-to-AGL conversion (footprintCalculator.ts:36). The trail noise calculator usesGROUND_ELEVATION_FT = 30ft (trailNoise.ts:22). The About page claims USGS National Elevation Dataset is used "where verified elevation data is available" and "55 feet MSL" as baseline — but the trail model uses 30 ft. This means the same aircraft at the same MSL altitude will produce different AGL values (and thus different noise estimates) depending on whether the footprint or trail code path is used.
FLAG — Repeated constants.
REFERENCE_DISTANCE_FT(1000),ATMOSPHERIC_ABSORPTION(0.5), andEARTH_RADIUS_FTare defined independently in four files with no shared constant module. The Earth radius values differ slightly (20,902,230.97 vs 20,902,231). This is cosmetic but indicates the constants were not derived from a single source.
3d. Weather adjustment model
lib/noise/weatherAdjustments.ts. Referenced standards: ISO 9613-2, ANSI S12.18, SAE-AIR-1845 (file header, lines 10–12).
Wind adjustment (calculateWindAdjustment(), lines 98–147):
| Condition | Speed | Angle to observer | Adjustment | Line |
|---|---|---|---|---|
| Calm | < 3 kts | any | 0 dB | 104 |
| Downwind, light | 3–10 kts | < 45° | +2 dB | 132 |
| Downwind, moderate | 10–20 kts | < 45° | +4 dB | 130 |
| Downwind, strong | > 20 kts | < 45° | +6 dB | 128 |
| Upwind, base | any | > 135° | −2 dB | 136 |
| Upwind, strong | > 10 kts | > 135° | −3 dB | 136+139 |
| Crosswind | any | 45°–135° | 0 dB | 143 |
Wind constants: WIND_CALM_THRESHOLD = 3 kts (line 57), WIND_MODERATE_THRESHOLD = 10 kts (line 58), WIND_STRONG_THRESHOLD = 20 kts (line 59).
Temperature inversion adjustment (calculateInversionAdjustment(), lines 187–210):
| Strength | Adjustment (within/above inversion) | Adjustment (below inversion) | Line |
|---|---|---|---|
| none | 0 dB | 0 dB | 78 |
| weak | +2 dB | +1 dB | 79, 209 |
| moderate | +5 dB | +2.5 dB | 80, 209 |
| strong | +8 dB | +4 dB | 81, 209 |
Below-inversion adjustment is full_adjustment × 0.5 (line 209).
Integration status: Weather adjustment is an optional parameter in calculateGroundNoise() (line 346). The trail noise model (trailNoise.ts) and footprint model (footprintCalculator.ts) do not pass weather data. No live weather feed is connected. The About page states: "The model uses standard atmospheric conditions" (line 1381 of about/page.tsx).
3e. Altitude-only models (trail and simple estimate)
The trail model (trailNoise.ts:60–71) and legacy simple estimate (trackNoiseCalculator.ts:634–661) both assume the listener is directly below the aircraft, using AGL altitude as the sole distance:
distanceAttenuation = 20 × log₁₀(effectiveAgl / 1000)
atmosphericAbsorption = 0.5 × (effectiveAgl / 1000)
groundDb = sourceDb − distanceAttenuation − atmosphericAbsorption
Differences:
- Trail model clamps AGL to minimum 50 ft (
trailNoise.ts:62). - Simple estimate clamps altitude to minimum 100 ft (
trackNoiseCalculator.ts:643). - Trail model uses
GROUND_ELEVATION_FT = 30for MSL→AGL. - Footprint model uses
KJPX_ELEVATION_FT = 55for MSL→AGL. - Neither applies lateral attenuation or weather adjustment.
3f. Multiple simultaneous aircraft
There is no explicit combination of simultaneous aircraft in the propagation model. Each aircraft's noise is computed independently. The DNL calculation (Section 4) sums acoustic energy across events over a time period, but there is no code that computes a combined instantaneous noise level from overlapping flights at a single point and time.
3g. Lateral attenuation — additional implementation
A second lateral attenuation module exists in components/noise/LateralAttenuation.ts (lines 35–122). It contains the same SAE-AIR-5662 table but adds:
- Aircraft category factors (lines 54–63): helicopter=0.7, jet=1.0, fixed_wing=0.85, unknown=1.0. These scale the base attenuation.
- Ground surface factors (lines 70–79): hard=0 dB, mixed=+1.5 dB, soft=+3.0 dB, absorptive=+4.5 dB. Applied for angles > 30°.
FLAG — Discrepancy. The
LateralAttenuation.tscomponent applies category-specific scaling factors (e.g., helicopter attenuation is only 70% of the table value) and ground surface corrections thattrackNoiseCalculator.tsdoes not. It is unclear which, if either, is currently used in a production code path. The category factors and ground surface adjustments have no cited provenance.
3h. TODOs and known issues
- No TODOs or FIXMEs in the propagation model files.
- The About page (
about/page.tsx:1376) discloses: "Terrain reflection and shielding. The model assumes unobstructed sound propagation." - The About page (
about/page.tsx:1381) discloses: "Weather effects. Wind speed, direction, temperature inversions, and humidity affect sound propagation. The model uses standard atmospheric conditions."
4. DNL Computation
4a. What the code does
DNL (Day-Night Average Sound Level, also written Ldn) is the FAA's primary metric for assessing aircraft noise impact. JPXWatch implements DNL computation in lib/noise/dnlCalculator.ts (349 lines).
The calculation has two stages: (1) compute SEL for each flight event, (2) aggregate SEL values with nighttime weighting into a single DNL number.
4b. SEL (Sound Exposure Level) calculation
calculateSEL() (dnlCalculator.ts:133–159):
SEL = L_ref − 20 × log₁₀(d_slant / d_ref) + 10 × log₁₀(t_exposure)
Where:
L_ref= reference noise level from EASA/FAA certification data (dB atd_ref)d_slant= slant distance from observer to closest approach (feet), clamped to min 1 ft (line 147)d_ref= 1,000 ft (line 143,REFERENCE_DISTANCE_FT)t_exposure= flyover duration in seconds, default 15 (line 142,DEFAULT_EXPOSURE_DURATION_SEC), clamped to min 0.1 s (line 148)
The first two terms compute the received maximum level (same inverse square law as the propagation model). The third term converts from instantaneous level to energy-integrated metric.
Result rounded to 1 decimal place (line 158).
Note. The SEL formula does not include atmospheric absorption or lateral attenuation. It uses only geometric spreading (inverse square law). This is a simpler model than
calculateGroundNoise()intrackNoiseCalculator.ts, which includes both atmospheric absorption and lateral attenuation.
4c. Nighttime classification
isNighttimeET() (dnlCalculator.ts:176–186):
Uses Intl.DateTimeFormat with IANA timezone 'America/New_York' (line 180) to convert UTC timestamps to Eastern Time. Returns true if hour ≥ 22 (10 PM) or hour < 7 (7 AM) (line 185).
FAA nighttime window: 10 PM – 7 AM ET (DNL_NIGHTTIME_START_HOUR = 22, line 51; DNL_NIGHTTIME_END_HOUR = 7, line 57).
FLAG — Two different "night" definitions.
- DNL nighttime: 10 PM – 7 AM ET (FAA standard,
dnlCalculator.ts:51,57)- Town curfew: 9 PM – 7 AM ET (
lib/constants/curfew.ts:10,13)This is correctly documented in the code: "The nighttime period for DNL (10 PM - 7 AM) differs from the East Hampton Town voluntary curfew (9 PM - 7 AM)" (
dnlCalculator.ts:21–24). The Noise Impact Score (Section 5) uses the curfew window (9 PM), not the DNL window.
4d. DNL aggregation formula
calculateDNL() (dnlCalculator.ts:212–230):
DNL = 10 × log₁₀( (1/T) × Σ( 10^(SELᵢ/10) × wᵢ ) )
Where:
T= reference time period in seconds =86400 × periodDays(line 217)SELᵢ= Sound Exposure Level for event iwᵢ= weighting: 1.0 for daytime, 10.0 for nighttime (line 222,NIGHTTIME_PENALTY_FACTOR = 10)
The 10× nighttime multiplier is the energy equivalent of the FAA's 10 dB penalty.
Returns 0 for empty input (line 214). Default periodDays = 1.
Result rounded to 1 decimal place (line 229).
4e. DNL constants
| Constant | Value | Unit | File:Line | Provenance |
|---|---|---|---|---|
FAA_DNL_THRESHOLD | 65 | dB DNL | dnlCalculator.ts:30 | 14 CFR Part 150 |
NIGHTTIME_PENALTY_FACTOR | 10 | multiplier (= 10 dB) | dnlCalculator.ts:36 | FAA standard |
DEFAULT_EXPOSURE_DURATION_SEC | 15 | seconds | dnlCalculator.ts:39 | Comment: "when track data is unavailable" |
REFERENCE_DISTANCE_FT | 1000 | ft | dnlCalculator.ts:42 | EASA/FAA certification standard |
SECONDS_PER_DAY | 86400 | seconds | dnlCalculator.ts:45 | Definition |
DNL_NIGHTTIME_START_HOUR | 22 | hour (ET) | dnlCalculator.ts:51 | FAA standard |
DNL_NIGHTTIME_END_HOUR | 7 | hour (ET) | dnlCalculator.ts:57 | FAA standard |
FLAG — Unattributed constant.
DEFAULT_EXPOSURE_DURATION_SEC = 15has no citation. The About page states "a standard 15-second flyover duration" (about/page.tsx:1361) but does not cite a source for this value. This constant significantly affects SEL and therefore DNL — a 30-second exposure produces 3 dB more SEL than a 15-second exposure.
4f. DNL severity categories
getDNLCategory() (dnlCalculator.ts:314–348):
| DNL range | Level | Color | Exceeds FAA threshold | FAA guidance | Lines |
|---|---|---|---|---|---|
| ≥ 75 dB | very_high | #DC2626 (red) | yes | Unacceptable for residential use | 315–321 |
| ≥ 65 dB | high | #EA580C (orange) | yes | Normally unacceptable for residential | 324–330 |
| ≥ 55 dB | moderate | #D97706 (amber) | no | Conditionally acceptable | 333–339 |
| < 55 dB | low | #16A34A (green) | no | Generally acceptable | 342–347 |
Referenced to FAA guidance in docstring (lines 298–309): 14 CFR Part 150.
4g. Time window
The periodDays parameter controls the averaging window:
- Default: 1 day (single 24-hour period)
- For seasonal DNL: the full season duration is passed (e.g., Memorial Day through Labor Day ≈ 100 days)
- The About page states DNL is "energy-averaged over the full season (Memorial Day through Labor Day)" (
about/page.tsx:1365)
DNL is not computed on a rolling basis — it is calculated on demand from stored flight events for the requested period.
4h. Convenience wrapper
calculateDNLFromFlights() (dnlCalculator.ts:253–294) combines SEL computation and DNL aggregation. For each flight, it:
- Calls
calculateSEL()with the flight'sreferenceDbandslantDistanceFt - Classifies as nighttime/daytime via
isNighttimeET() - Aggregates all events via
calculateDNL()
Returns a DNLResult with: dnl, events[], nighttimeCount, daytimeCount.
4i. Referenced standards
Listed in file header (dnlCalculator.ts:16–19):
- FAA Order 1050.1F, Appendix B: Noise and Noise-Compatible Land Use
- 14 CFR Part 150: Airport Noise Compatibility Planning
- FICON (Federal Interagency Committee on Noise), 1992
- EASA Certification Noise Levels database (source noise data)
4j. TODOs and known issues
- No TODOs or FIXMEs in
dnlCalculator.ts. - The SEL formula omits atmospheric absorption and lateral attenuation, making it a simpler model than the track-based propagation. This is not flagged in the code as intentional or unintentional.
- Empty input returns 0 (line 214), not null or undefined. A location with zero flights during a period shows DNL=0 rather than "no data."
5. Noise Rank
5a. What the code does
"Noise Rank" in JPXWatch encompasses two distinct metrics:
- Noise Impact Score — a 0–100 composite metric combining fleet mix, traffic density, and time-of-day. Computed client-side. File:
lib/calculations/noiseImpactScore.ts. - NoiseRank Percentile — a location-specific percentile ranking based on estimated DNL at the user's address compared to a grid of points. Computed server-side via PostGIS. Displayed on the "My Home" page.
5b. Noise Impact Score formula
calculateNoiseImpactScore() (noiseImpactScore.ts:101–121):
score = round( fleetMixScore × 0.4 + concentrationScore × 0.3 + timeOfDayScore × 0.3 )
Component 1: Fleet Mix (40%) — calculateFleetMixScore() (lines 23–41):
- Counts "loud" operations: all helicopters + jets where
getNoiseDb(f) >= LOUD_THRESHOLD_DB(85 dB). - Formula:
min(100, (loudCount / totalOps) × 500). - Scale: 20% loud operations = score of 100.
- Per flight, not per operator.
Component 2: Concentration (30%) — calculateConcentrationScore() (lines 50–56):
- Measures operations density.
- Formula:
min(100, avgOpsPerDay × 2). - Scale: 50 ops/day = score of 100.
Component 3: Time-of-Day (30%) — calculateTimeOfDayScore() (lines 65–76):
- Counts curfew-period operations:
operation_hour_et >= 21 || operation_hour_et < 7(line 70). - This uses the town curfew window (9 PM – 7 AM), not the FAA nighttime window (10 PM – 7 AM).
- Formula:
min(100, (curfewOps / totalOps) × 1000). - Scale: 10% curfew operations = score of 100.
5c. Severity tiers
getSeverity() (noiseImpactScore.ts:87–92):
| Score | Severity | Description (from docstring) | Line |
|---|---|---|---|
| 80–100 | SEVERE | Extreme noise burden requiring immediate attention | 88 |
| 65–79 | HIGH | Significant community impact | 89 |
| 40–64 | MODERATE | Noticeable noise burden | 90 |
| 0–39 | LOW | Minimal community impact | 91 |
5d. Aggregate score variant
calculateNoiseImpactScoreFromAggregates() (noiseImpactScore.ts:128–170) is used when per-flight dB data is unavailable (e.g., monthly aggregates). It takes pre-aggregated counts: totalOps, helicopters, jets, curfewOps, daysWithData.
Critical difference: This variant treats all jets as loud (line 150: loudCount = helicopters + jets) because per-flight dB data is not available in the aggregate. This overestimates the fleet mix score.
FLAG — Known overestimation documented in code. Lines 145–149: "For monthly aggregates we don't have per-flight dB data, so we treat ALL jets as loud (≥85 dB). This overestimates fleetMixScore because many jets at KJPX are light jets below 85 dB." TODO on line 149: "Add a
loud_jetscolumn to monthly aggregates to improve accuracy."
5e. NoiseRank Percentile
Implemented in app/my-home/page.tsx (lines 449–546, approximate — this is a large React page).
Season determination:
- If current month ≥ 5 (May): season year = current year.
- If current month < 5: season year = previous year (continues prior summer season).
- Season label: "Memorial Day YYYY - Today" (during season) or "Summer YYYY (Final)" (after season).
Percentile computation:
- Calls PostGIS RPC
nearest_noise_gridwith user's lat/lon to find the nearest noise grid point and its estimated DNL. - Calls PostGIS RPC
noise_rank_percentilewith that DNL value and season year to get the percentile rank (0–100). - Percentile represents: "X% of grid points have lower noise than this location."
Year-over-year comparison:
- Fetches previous season's (year − 1) DNL and percentile for the same location.
- Displayed as
previousPercentilealongside current percentile. - Fails silently if prior year data unavailable.
Demo fallback values (when user not authenticated or no data):
dnl: 62.4,flightCount: 847,percentile: 90,totalPoints: 30000,seasonLabel: 'Summer 2025 (Demo)',previousPercentile: 85.
5f. Percentile display bands
components/noiserank/PercentileCard.tsx (lines 25–66):
| Range | Label | Color |
|---|---|---|
| 0–20 | Low | emerald |
| 20–40 | Below average | teal |
| 40–60 | Average | amber |
| 60–80 | Above average | orange |
| 80–100 | High | red |
5g. DNL display on "My Home"
components/noiserank/DNLCard.tsx (lines 31–187):
- DNL bar visualization with four segments: 0–55 (green), 55–65 (amber), 65–75 (orange), 75–85 (red).
- Dashed marker at 65 dB (FAA threshold).
- Triangle indicator at user's DNL value.
- Shows flight count and season label.
5h. How ties are broken / sparse data handled
- Ties: No explicit tie-breaking. The PostGIS
noise_rank_percentilefunction returns a percentile; behavior on ties depends on the server-side RPC implementation (not in the client codebase). - Sparse data: If zero flights exist for a period,
calculateNoiseImpactScorereturns score=0, severity='LOW', all components=0. The percentile system relies on pre-computed noise grid data; if the grid is empty, the RPC returns null and the UI falls back to demo values.
5i. TODOs and known issues
noiseImpactScore.ts:149: TODO to addloud_jetscolumn to monthly aggregates.- The Noise Impact Score is per-period (day/week/month), not per-aircraft or per-operator. It describes the aggregate noise environment, not individual actor responsibility.
- The percentile grid computation (PostGIS RPCs) is not in the client codebase; the audit can only document the client's interaction with those RPCs.
6. Noise Trails
6a. What is being visualized
Noise Trails are animated flight paths on the map where each track point is colored by its estimated ground-level noise. The visualization shows:
- A colored polyline tracing the aircraft's GPS path
- Color at each point reflects the estimated dB a listener directly below would hear
- Line width at each point reflects altitude (lower = wider = more impact)
- An aircraft dot at the current position
File: lib/noise/trailNoise.ts (159 lines).
6b. Underlying computation
computeNoiseTrail() (trailNoise.ts:90–159) processes raw FlightAware track positions into NoiseTrailPoint[]:
- Profile lookup (line 102):
getAircraftNoiseProfile(aircraftType)— uses the 28-type client-side table, not the 47-type EASA map. - Source dB selection (line 103): arrivals →
approachDb, departures →takeoffDb. - Point ordering (lines 108–126): Sorts by distance to KJPX (40.9594, −72.2518) so that arrivals animate origin→KJPX and departures animate KJPX→destination. Uses Euclidean distance on lat/lon (not Haversine).
- Altitude conversion (lines 139–141):
- FlightAware altitude is in hundreds of feet (flight levels) — multiplied by 100 (line 140).
- MSL to AGL:
altitudeAgl = altitudeMsl − GROUND_ELEVATION_FTwhereGROUND_ELEVATION_FT = 30(line 22).
- Noise estimation (line 142):
estimateGroundNoise(referenceDb, altitudeAgl)— altitude-only model, assumes listener directly below. - Trail width (line 143):
getTrailWidth(altitudeAgl). - Trail color (line 144):
getNoiseColorFromDb(noiseDb).
6c. Ground noise formula (trail-specific)
estimateGroundNoise() (trailNoise.ts:60–71):
effectiveAgl = max(aglFt, 50)
distanceAttenuation = 20 × log₁₀(effectiveAgl / 1000)
atmosphericAbsorption = 0.5 × (effectiveAgl / 1000)
groundDb = referenceDb − distanceAttenuation − atmosphericAbsorption
This is the altitude-only simplification: no horizontal distance, no lateral attenuation, no weather. The listener is assumed to be directly below the aircraft.
Minimum AGL clamp: 50 ft (line 62). Result rounded to 1 decimal place.
6d. Filtering
Track points are filtered before processing (trailNoise.ts:129–136):
altitude == nulloraltitude <= 0→ excluded (line 130).altitude <= 1(≤ 100 ft MSL) → excluded as ground/taxi (line 134).altitude <= 2(≤ 200 ft MSL) ANDgroundspeed < 60kts → excluded as likely ground (line 135).
No time-window filtering — all valid track points from the FlightAware response are included.
6e. Color thresholds
getNoiseColorFromDb() (components/map/mapConstants.ts:17–22):
| Threshold | Color | Category label | Line |
|---|---|---|---|
| ≥ 85 dB | #ef4444 (red) | veryLoud | 18 |
| ≥ 75 dB | #f97316 (orange) | loud | 19 |
| ≥ 65 dB | #eab308 (yellow) | moderate | 20 |
| < 65 dB | #22c55e (green) | quiet | 21 |
These are the same four colors used across the map UI. The 65/75/85 dB boundaries are defined in mapConstants.ts and imported by the trail computation.
6f. Trail width thresholds
getTrailWidth() (trailNoise.ts:77–82):
| AGL altitude | Width | Meaning |
|---|---|---|
| ≤ 500 ft | 12 px | Very low altitude, high ground impact |
| ≤ 1,000 ft | 8 px | Low altitude |
| ≤ 2,000 ft | 5 px | Medium altitude |
| > 2,000 ft | 3 px | High altitude, lower impact |
Provenance: Not cited. These are visual design parameters.
6g. Animation rendering
components/map/AnimatedFlightLayer.tsx renders the computed trail data:
- Glow layer (lines 140–156): 6 px width, 0.15–0.3 opacity, 4 px blur. Creates a soft halo effect.
- Core line (lines 158–169): 2 px width, 0.7 opacity, round cap/join.
- Aircraft dot: 4.5–6 px radius with white halo, noise-colored core.
All colors come from the pre-computed trail_color field on each NoiseTrailPoint.
6h. Data pipeline
Live tracks are served by GET /api/flights/live-tracks with a since parameter (ISO timestamp):
- Default window: last 60 minutes.
- Cache TTL: 30 seconds.
- Max window: 120 minutes.
- Each trail includes
peak_noise_db(max across points) andavg_noise_db(mean across points).
6i. Smoothing and aggregation
None. Each track point is independently computed from its raw GPS position and altitude. There is no temporal smoothing, spatial averaging, or interpolation between points. The visualization shows raw per-point estimates.
6j. TODOs and known issues
- No TODOs or FIXMEs in
trailNoise.ts. - The trail model uses
GROUND_ELEVATION_FT = 30(line 22), while the footprint model usesKJPX_ELEVATION_FT = 55(see Section 3c flag). - The trail model uses
getAircraftNoiseProfile()(28-type client-side table) whiletrackNoiseCalculator.tsusesgetEASANoiseProfile()(47-type EASA table). An aircraft type present in the EASA map but absent from the client-side table will get different dB values in trails vs. track-based analysis. - The point-ordering algorithm uses Euclidean distance on lat/lon (
Math.sqrt((p.latitude - KJPX_LAT)² + (p.longitude - KJPX_LON)²), line 111), not Haversine. At KJPX's latitude (~41°N), one degree of longitude ≈ 0.755× one degree of latitude, introducing minor distortion. This is unlikely to cause incorrect ordering in practice.
7. High-Noise Events
7a. What the code does
A "high-noise event" is a flight that produces an estimated ground-level noise of ≥ 65 dB at a specific location. This threshold is the FAA's DNL significance threshold applied to individual flyover events. The feature counts how many flights exceeded this level at the user's address during a season.
7b. Threshold definition
| Property | Value | Source |
|---|---|---|
| Threshold | 65 dB | FAA_DNL_THRESHOLD = 65 in lib/noise/dnlCalculator.ts:30 |
| Units | dBA LAmax (A-weighted maximum sound level, estimated) | Derived from EASA/FAA certification data |
| Measurement | Physics-based estimate, not physical measurement | No noise monitors installed at KJPX |
The 65 dB value comes from 14 CFR Part 150, which defines 65 dB DNL as the threshold for "significant" noise exposure. JPXWatch applies this same threshold to individual flyover events, not just to 24-hour DNL averages.
The About page confirms: "No monitors are installed at JPX" (about/page.tsx:1238). The file lib/noise/getNoiseDb.ts:15 states: "No physical noise monitoring equipment is currently installed at JPX."
7c. How events are measured
The noise at a location is computed using the propagation model (see Section 3):
groundDb = sourceDb − 20 × log₁₀(slantDist / 1000) − 0.5 × (slantDist / 1000)
Where slantDist = √(altitude_ft² + horizontalDistance_ft²).
For the "My Home" feature, noise is computed per track position against the user's saved address. For the residential analysis, noise is computed per flight per parcel using the same formula.
In trackNoiseCalculator.ts (line 539): events where estimate.db >= 65 accumulate timeAboveThreshold (counted in seconds, assuming 5-second position intervals, line 514).
7d. Configurability
The 65 dB threshold is not configurable:
- Hardcoded as
FAA_DNL_THRESHOLDindnlCalculator.ts:30. - Hardcoded in UI tooltip text in
components/noiserank/HighNoiseEventsCard.tsx(line 29): "Flights where estimated noise at your address exceeded 65 dB, the FAA's threshold for significant noise exposure." - No environment variable, feature flag, or per-user/per-location setting exists.
- The threshold is the same globally for all users and all locations.
7e. How events are counted
Events are counted per aircraft pass per location. A single flight that produces ≥ 65 dB at one or more track positions at the user's location counts as one high-noise event. The system does not count per-dB-exceedance or per-time-window.
In the residential analysis (docs/high-noise-events-residential-analysis-v3.md), the counting methodology uses a two-stage filter:
- Range pre-filter: Each aircraft type has a pre-computed maximum horizontal distance at which it can produce ≥ 65 dB. Flight-parcel pairs beyond this range are skipped without full noise computation.
- Full attenuation filter: For pairs that pass the range check, the full propagation formula is applied. Only events ≥ 65 dB are counted.
7f. Noise Index (related metric)
The "Noise Index" is a related but different count, defined in lib/noise/getNoiseDb.ts:60–73:
- Counts: all helicopter operations + jets where estimated noise ≥ 85 dB (
LOUD_THRESHOLD_DB, line 22). - The 85 dB threshold is for aircraft-level source noise (at 1,000 ft reference), not ground-level received noise.
- This is a fleet-composition metric (how many loud aircraft types operated), not a location-based impact metric.
7g. Display
components/noiserank/HighNoiseEventsCard.tsx:
- Shows count of high-noise events out of total flights.
- Text: "{count} of {total} flights above 65 dB at your address."
7h. About page discrepancy
FLAG — "Est. Noise" column inconsistency. The residential analysis document (
docs/high-noise-events-residential-analysis-v3.md, lines 119–130) notes that the "My Home" feature's "Est. Noise" column displays raw certification noise (source dB at 1,000 ft reference) rather than distance-attenuated noise. This means the column shows e.g., 88 dB for an S76 regardless of the flight's actual distance from the user. The High-Noise Events count, by contrast, uses fully attenuated noise. This creates an inconsistency where the displayed "Est. Noise" for a flight may be well above 65 dB while the flight does not count as a high-noise event (because attenuated noise was below 65 dB), or vice versa for a flight that passed very close at low altitude.
7i. TODOs and known issues
- No TODOs or FIXMEs in the high-noise event code paths.
- The 65 dB threshold is borrowed from the FAA's DNL metric (a 24-hour energy average) and applied to individual flyover peak levels. These are different acoustic metrics — a single flight producing 65 dB LAmax at a point is not the same as experiencing 65 dB DNL over a full day. The code does not document this distinction.