# ТЗ — Слой "Плотность пролётов"

**Версия:** 1.0  
**Дата:** 2026-03-27  
**Прототип:** noisemap v0.2  

---

## 1. Цель

Добавить на карту слой визуализации частоты пролётов самолётов над каждой точкой Московской области. Пользователь должен видеть не отдельные рейсы, а суммарную картину — где самолёты летают чаще всего на малой высоте.

---

## 2. Текущий стек

| Компонент | Технология |
|-----------|------------|
| Backend | Python 3.11, Flask, порт 5555 |
| Frontend | OpenLayers 10 (Canvas2D), Turf.js 6 |
| Данные | JSON-файлы `data/flights_*.json` |
| Модель шума | `noise_model.py` — теорема Пифагора, 4 зоны |
| Деплой | nginx → Flask :5555, URL: `/noisemap/` |

---

## 3. Что нужно сделать

### 3.1 Backend — новый endpoint `/api/density`

**Файл:** `app.py` — добавить route `/api/density`  
**Вспомогательный модуль:** `density_model.py` (новый файл)

#### Алгоритм расчёта сетки

1. Загрузить все рейсы из `data/flights_*.json`
2. Определить bbox по всем точкам треков
3. Создать сетку ячеек **500×500 м** (≈40 000 ячеек для МО)
4. Для каждой точки трека:
   - Определить высоту в метрах (`altitude_m`)
   - Если `altitude_m ≥ 7000` → пропустить
   - Определить радиус влияния по таблице:

| Высота | Радиус |
|--------|--------|
| H < 1800 м | R = 2 км |
| H < 5000 м | R = 4 км |
| H < 7000 м | R = 7 км |
| H ≥ 7000 м | не считать |

   - Найти все ячейки сетки в радиусе R от точки
   - Для каждой ячейки: `count += 1`, обновить `min_altitude_m`

5. Дедупликация: одна точка трека может попасть в ячейку несколько раз подряд (самолёт летит медленно). Считать не точки, а **рейсы** — каждый рейс учитывается в ячейке не более 1 раза.

#### Формат ответа `/api/density`

```json
{
  "grid_size_m": 500,
  "bbox": [lon_min, lat_min, lon_max, lat_max],
  "cells": [
    {
      "lat": 55.921,
      "lon": 37.418,
      "count": 47,
      "min_altitude_m": 312
    },
    ...
  ],
  "total_cells": 1243,
  "flights_used": 258,
  "generated_at": "2026-03-27T05:00:00Z"
}
```

Отдавать **только ячейки с `count > 0`** — остальные пустые.

#### Кэширование

- Результат кэшировать в `data/density_cache.json`
- Пересчитывать если файл старше 1 часа или отсутствует
- Query param `?refresh=1` — принудительный пересчёт

#### Нефункциональные требования

- Расчёт < 10 сек для 50 000 точек
- Размер ответа < 2 МБ
- Модуль `density_model.py` — отдельно от `noise_model.py`

---

### 3.2 Frontend — новый слой на карте

**Файл:** `index.html`

#### Переключатель в панели слоёв

Добавить в секцию управления слоями (рядом с "Треки" и "Шумовые зоны"):

```
[ ] Плотность пролётов
```

- Чекбокс, по умолчанию **выключен**
- При включении — загружает `/api/density` (если ещё не загружено) и показывает слой
- При выключении — скрывает слой (данные не перезагружать)

#### Визуализация

Реализовать через **OpenLayers Heatmap layer** (`ol/layer/Heatmap`):

- Источник: `ol/source/Vector` с точками из `cells[]`
- Вес точки = `count` (нормализованный от 0 до 1 по max_count)
- Радиус размытия: 20px
- Цветовая шкала (gradient):
  ```javascript
  ['#0000FF', '#00FFFF', '#FFFF00', '#FF8800', '#FF0000']
  // синий → голубой → жёлтый → оранжевый → красный
  ```
- Прозрачность слоя: 0.65
- zIndex: между подложкой и треками (zIndex = 5)

#### Попап при клике на ячейку

При клике на карту (когда слой активен) — показать попап если в радиусе 500 м от клика есть ячейка с `count > 0`:

```
📍 Плотность пролётов
─────────────────────
Рейсов над точкой: 47
Мин. высота: 312 м
```

Попап закрывается кликом в другое место.

---

## 4. Порядок реализации

1. `density_model.py` — расчётный модуль, тест на реальных данных
2. `/api/density` в `app.py` — endpoint с кэшированием
3. Frontend: переключатель + heatmap слой
4. Frontend: попап при клике
5. Тест на реальных данных (20–21 марта + 26 марта)

---

## 5. Что НЕ входит в scope

- Ночной штраф / разбивка по времени суток
- Фильтрация по аэропорту или типу ВС
- Экспорт данных плотности
- NPD-модель шума

---

## 6. Файлы к изменению / созданию

| Файл | Действие |
|------|----------|
| `density_model.py` | создать |
| `app.py` | добавить `/api/density` |
| `index.html` | добавить слой + переключатель + попап |
| `data/density_cache.json` | генерируется автоматически |
