""" Модель шумового загрязнения от воздушных судов (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 = √(25−12.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.0–1.0) Итоговая прозрачность умножается на altitude_factor ALTITUDE_BANDS — как высота влияет на ширину зон. max_alt_m - верхняя граница диапазона высоты (метры) width_factor - коэффициент ширины зоны (1.0 = полная, 0.0 = зона исчезает) Диапазоны проверяются снизу вверх, берётся первый подходящий. Пример калибровки: Если реальные замеры показывают, что на высоте 500м зона 0–2км слишком широкая — уменьши width_factor для диапазона max_alt_m=900. """ # ── Зоны шума ──────────────────────────────────────────────────── # # Физическая модель (теорема Пифагора): # # самолёт ● # |\ # H | \ R ← гипотенуза = реальное расстояние до наблюдателя # | \ # земля ●───────●──────● наблюдатель # проекция D ← катет = ширина зоны на карте # # D = √(R² − H²), если H < R, иначе 0 # # Поля зоны: # R_inner — внутренняя граница сферы (км); для первой зоны = 0 # R_outer — внешняя граница сферы (км) # color — цвет заливки (hex) # opacity — прозрачность (фиксированная, 0.0–1.0) # # Таблица соответствия: # R < 2 км → критический шум 🔴 # R 2–5 км → сильный шум 🟠 # R 5–7 км → средний шум 🟡 # R 7–9 км → низкий шум 🟢 # 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 2–5 км)", "R_inner": 2.0, "R_outer": 5.0, "color": "#FF8800", "opacity": 0.01, }, { "id": "zone_medium", "label": "Средний (R 5–7 км)", "R_inner": 5.0, "R_outer": 7.0, "color": "#FFCC00", "opacity": 0.01, }, { "id": "zone_low", "label": "Низкий (R 7–9 км)", "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: """ Цветовая кодировка по высоте: - Красный (0–3000 ft): высокий шум - Оранжевый (3000–10000 ft): средний шум - Жёлтый (10000–25000 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.1–0.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), }