Files
wiki/tasks/flightradar24/ingest/mart/noise_model.py
2026-04-20 23:20:01 +03:00

297 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Модель шумового загрязнения от воздушных судов (v1.1)
Физическая основа
─────────────────
Шум распространяется сферически. Уровень шума определяется
реальным 3D-расстоянием R (гипотенуза) от самолёта до наблюдателя.
На карте отображается горизонтальный катет D:
самолёт ●
|\
H | \ R ← граница зоны
| \
земля ●───●─────● наблюдатель
D
D = √(R² H²), если H < R, иначе 0
Пример (H = 3.5 км):
R=2 км → нет (2² < 3.5²)
R=5 км → D = √(2512.25) = 3.57 км (круг)
R=7 км → D = 6.06 км, кольцо от 3.57 до 6.06 км
R=11 км → D = 10.43 км, кольцо от 6.06 до 10.43 км
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
КАЛИБРОВОЧНЫЕ ПАРАМЕТРЫ (редактируй здесь)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
NOISE_ZONES — три концентрических зоны вдоль траектории.
Каждая зона описывает «рукав» определённой ширины рядом с треком.
Поля каждой зоны:
id - уникальный идентификатор (используется в JS)
label - отображаемое название в легенде
dist_km - внешняя граница зоны от трека (км)
Зона 0 рисуется от 0 до dist_km[0],
Зона 1 — от dist_km[0] до dist_km[1], и т.д.
color - цвет заливки (hex)
opacity - базовая прозрачность при полной активации (0.01.0)
Итоговая прозрачность умножается на altitude_factor
ALTITUDE_BANDS — как высота влияет на ширину зон.
max_alt_m - верхняя граница диапазона высоты (метры)
width_factor - коэффициент ширины зоны (1.0 = полная, 0.0 = зона исчезает)
Диапазоны проверяются снизу вверх, берётся первый подходящий.
Пример калибровки:
Если реальные замеры показывают, что на высоте 500м зона 02км
слишком широкая — уменьши width_factor для диапазона max_alt_m=900.
"""
# ── Зоны шума ────────────────────────────────────────────────────
#
# Физическая модель (теорема Пифагора):
#
# самолёт ●
# |\
# H | \ R ← гипотенуза = реальное расстояние до наблюдателя
# | \
# земля ●───────●──────● наблюдатель
# проекция D ← катет = ширина зоны на карте
#
# D = √(R² H²), если H < R, иначе 0
#
# Поля зоны:
# R_inner — внутренняя граница сферы (км); для первой зоны = 0
# R_outer — внешняя граница сферы (км)
# color — цвет заливки (hex)
# opacity — прозрачность (фиксированная, 0.01.0)
#
# Таблица соответствия:
# R < 2 км → критический шум 🔴
# R 25 км → сильный шум 🟠
# R 57 км → средний шум 🟡
# R 79 км → низкий шум 🟢
# R > 9 км → зона не рисуется
#
NOISE_ZONES = [
{
"id": "zone_critical",
"label": "Критический (R < 2 км)",
"R_inner": 0.0, # км — внутренняя граница сферы
"R_outer": 2.0, # км — внешняя граница сферы
"color": "#FF3333",
"opacity": 0.01,
},
{
"id": "zone_strong",
"label": "Сильный (R 25 км)",
"R_inner": 2.0,
"R_outer": 5.0,
"color": "#FF8800",
"opacity": 0.01,
},
{
"id": "zone_medium",
"label": "Средний (R 57 км)",
"R_inner": 5.0,
"R_outer": 7.0,
"color": "#FFCC00",
"opacity": 0.01,
},
{
"id": "zone_low",
"label": "Низкий (R 79 км)",
"R_inner": 7.0,
"R_outer": 9.0,
"color": "#88DD00",
"opacity": 0.01,
},
]
# ALTITUDE_BANDS больше не используется — ширина зоны теперь
# рассчитывается аналитически через теорему Пифагора в calc_horizontal_radius()
ALTITUDE_BANDS = [] # оставлен для обратной совместимости
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# КОНЕЦ КАЛИБРОВОЧНЫХ ПАРАМЕТРОВ
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Справочные данные: типичные уровни шума у земли (дБ)
# Источник: стандартные авиационные данные
NOISE_AT_GROUND = {
"default": 85, # дефолт для неизвестного типа ВС
"B738": 88, # Boeing 737-800
"B77W": 90, # Boeing 777-300ER
"A320": 87, # Airbus A320
"A321": 88, # Airbus A321
"A333": 89, # Airbus A330-300
"A359": 86, # Airbus A350-900
"B763": 89, # Boeing 767-300
"SU95": 86, # Sukhoi Superjet 100
"E170": 84, # Embraer 170
"AT75": 80, # ATR 72
}
# Параметры модели
MAX_NOISE_RADIUS_KM = 3.0 # максимальный радиус шумовой зоны (км) на нулевой высоте
MIN_ALTITUDE_FT = 100 # минимальная высота для расчёта (фут)
MAX_ALTITUDE_FT = 40000 # максимальная высота (фут) — шум не слышен выше
NOISE_THRESHOLD_DB = 55 # порог шума (дБ), ниже которого зона не показывается
def altitude_to_noise_db(altitude_ft: float, aircraft_type: str = "default") -> float:
"""
Расчёт уровня шума на земле в зависимости от высоты (дБ)
Формула: L = L0 - 20*log10(h/h0) - α*h
где L0 - шум у земли, h - высота, h0 = 300 ft (опорная высота), α = коэф. затухания
"""
base_noise = NOISE_AT_GROUND.get(aircraft_type, NOISE_AT_GROUND["default"])
if altitude_ft <= MIN_ALTITUDE_FT:
return base_noise
if altitude_ft >= MAX_ALTITUDE_FT:
return 0.0
# Геометрическое затухание (обратный квадрат расстояния → 20 log)
import math
h0 = 300 # опорная высота в футах
geometric_attenuation = 20 * math.log10(altitude_ft / h0)
# Атмосферное поглощение (приблизительно 0.002 дБ/фут)
atmospheric_attenuation = 0.002 * altitude_ft
noise_db = base_noise - geometric_attenuation - atmospheric_attenuation
return max(0.0, noise_db)
def altitude_to_noise_radius_km(altitude_ft: float) -> float:
"""
Расчёт радиуса шумовой зоны (км) на основе высоты
Простая обратно-пропорциональная модель для визуализации
"""
if altitude_ft <= 0:
altitude_ft = 100
if altitude_ft >= MAX_ALTITUDE_FT:
return 0.0
# Радиус уменьшается с высотой (нелинейно)
radius = MAX_NOISE_RADIUS_KM * (1.0 - (altitude_ft / MAX_ALTITUDE_FT) ** 0.5)
return max(0.0, radius)
def altitude_to_color(altitude_ft: float) -> str:
"""
Цветовая кодировка по высоте:
- Красный (03000 ft): высокий шум
- Оранжевый (300010000 ft): средний шум
- Жёлтый (1000025000 ft): низкий шум
- Зелёный (25000+ ft): минимальный шум
"""
if altitude_ft < 3000:
return "#FF0000" # красный - критический шум
elif altitude_ft < 10000:
return "#FF6600" # оранжевый - высокий шум
elif altitude_ft < 25000:
return "#FFAA00" # жёлтый - средний шум
else:
return "#00AA44" # зелёный - низкий шум
def altitude_to_noise_level(altitude_ft: float) -> str:
"""Текстовое описание уровня шума"""
if altitude_ft < 3000:
return "Критический"
elif altitude_ft < 10000:
return "Высокий"
elif altitude_ft < 25000:
return "Средний"
else:
return "Низкий"
def calculate_noise_opacity(altitude_ft: float) -> float:
"""Прозрачность шумовой зоны (0.10.6)"""
if altitude_ft >= MAX_ALTITUDE_FT:
return 0.0
opacity = 0.6 * (1.0 - altitude_ft / MAX_ALTITUDE_FT)
return max(0.05, min(0.6, opacity))
def calc_horizontal_radius(R_km: float, altitude_m: float) -> float:
"""
Горизонтальный радиус зоны на карте (катет) по теореме Пифагора.
R_km — радиус сферы шума (км), граница зоны
altitude_m — высота самолёта над землёй (метры)
Возвращает D в км, или 0 если самолёт выше границы зоны.
"""
import math
H = altitude_m / 1000.0 # переводим в км
if H >= R_km:
return 0.0
return math.sqrt(max(0.0, R_km**2 - H**2))
def calc_zone_radii_for_point(altitude_m: float) -> list:
"""
Для каждой зоны возвращает (D_inner, D_outer) в км на земле.
Если D_outer == 0 → зона не видна.
Если D_inner == 0 → зона рисуется как круг (без дырки).
"""
result = []
for zone in NOISE_ZONES:
d_inner = calc_horizontal_radius(zone["R_inner"], altitude_m) if zone["R_inner"] > 0 else 0.0
d_outer = calc_horizontal_radius(zone["R_outer"], altitude_m)
result.append({
"id": zone["id"],
"color": zone["color"],
"opacity": zone["opacity"],
"d_inner": round(d_inner, 4), # км, внутренняя граница на карте
"d_outer": round(d_outer, 4), # км, внешняя граница на карте
"visible": d_outer > 0.0,
})
return result
def get_noise_config() -> dict:
"""
Возвращает калибровочные параметры для фронтенда.
Вызывается через /api/noise-config — JS читает конфиг при старте.
"""
return {
"zones": NOISE_ZONES,
"altitude_bands": ALTITUDE_BANDS,
}
def get_altitude_width_factor(altitude_m: float) -> float:
"""Возвращает коэффициент ширины зоны для данной высоты (метры)."""
for band in ALTITUDE_BANDS:
if altitude_m <= band["max_alt_m"]:
return band["width_factor"]
return 0.0
def process_flight_for_map(flight_data: dict) -> dict:
"""
Обрабатывает данные одного рейса и добавляет шумовые характеристики
"""
altitude = flight_data.get("altitude", 0) or 0
aircraft_type = flight_data.get("aircraft_type", "default") or "default"
return {
**flight_data,
"noise_db": round(altitude_to_noise_db(altitude, aircraft_type), 1),
"noise_radius_km": round(altitude_to_noise_radius_km(altitude), 3),
"noise_color": altitude_to_color(altitude),
"noise_level": altitude_to_noise_level(altitude),
"noise_opacity": round(calculate_noise_opacity(altitude), 3),
}