# Формулы и расчёты RadioApi Документ описывает расчётные модели, формулы и ограничения, реализованные в **RadioApi**. **Единицы по умолчанию** | Величина | Единица | |----------|---------| | Координаты | градусы WGS84 (`lat`, `lon`) | | Расстояние | м или км | | Высота | м AGL или м AMSL | | Частота | МГц в API, Гц/ГГц во внутренних моделях | | Мощность | дБм | | Усиление | дБи | | Потери | дБ | Префикс API: `/api/v1`. ## 1. Геометрия и профиль пути Файл: `api/app/core/geo.py` ### Haversine Приближённое расстояние по сфере: $$ a = \sin^2\frac{\Delta\varphi}{2} + \cos\varphi_1 \cos\varphi_2 \sin^2\frac{\Delta\lambda}{2} $$ $$ d = 2 R_e \arcsin(\sqrt{a}) $$ где $R_e = 6\,371\,000$ м. Функция: `haversine(a, b)` → расстояние в метрах. ### Геодезическая линия WGS84 Функция: `sample_path(start, end, n)`. Точки строятся через `pyproj.Geod` на эллипсоиде WGS84: $$ d_i = \begin{cases} i \cdot D/(n-1), & i < n-1 \\ D, & i = n-1 \end{cases} $$ где $D$ — полная геодезическая длина. ### DEM Файл: `api/app/core/dem.py` Высоты выбираются из GeoTIFF/COG: - `elevation_at(lat, lon)` — одна точка; - `elevations_along(points)` — массив точек. Используется в `/terrain/elevation`, `/terrain/profile`, `/terrain/los`, `/terrain/fresnel-slice`, `/link/budget`, `/coverage`. ## 2. Профиль поверхности Файл: `api/app/core/surface.py` Для каждой точки пути: $$ h_{surface} = h_{ground} + \max(h_{building}, h_{canopy}) $$ | Поле | Описание | |------|----------| | `ground_m` | DEM, м AMSL | | `building_m` | высота здания, м | | `canopy_m` | высота кроны из `CANOPY_PATH`, м | | `surface_m` | итоговая высота препятствия | Canopy является optional layer: если `include_canopy=false`, файлов нет или отдельные точки не покрыты canopy-тайлами, соответствующая высота кроны считается `0.0`. Тип препятствия (`dominant_obstruction`): `building` → `canopy` → `terrain`. API: `/terrain/profile`, `/terrain/los`, `/terrain/fresnel-slice`, `/link/budget`. ## 3. FSPL Файл: `api/app/core/propagation.py` $$ L_{FS} = 32.44 + 20 \log_{10}(f_{MHz}) + 20 \log_{10}(d_{km}) $$ Входы: $f_{MHz} > 0$, $d_{km} > 0$. Используется в `/coverage` (`model=fspl`) и как базовая составляющая `/link/budget`. ## 4. Бюджет радиолинии Файлы: `api/app/core/propagation.py`, `api/app/services/link.py`. $$ P_{rx} = P_{tx} + G_{tx} + G_{rx} - L_{total} $$ $$ fade\_margin = P_{rx} - P_{sens} $$ $$ link\_viable = (fade\_margin > 0) $$ ### Активные слагаемые по модели В ответе API есть отдельные диагностические поля: | Поле | Смысл | |------|-------| | `propagation_model_loss_db` | полная потеря выбранной модели распространения до vegetation/atmosphere | | `propagation_excess_db` | избыток выбранной модели над FSPL | | `knife_edge_diffraction_db` | Bullington/P.526 diagnostic loss по surface profile | | `diffraction_db` | active diffraction-like term, оставлен для обратной совместимости | | `model` | Суммарные потери `total_loss_db` | Что не добавляется отдельно | |---------|----------------------------------|------------------------------| | `manual` | $L_{FS} + L_{diff} + L_{veg} + L_{atm}$ | нет | | `itm` | $L_{ITM} + L_{veg} + L_{atm}$ | отдельный Bullington/P.526 | | `p452` | $L_{P.452} + L_{veg}$ | отдельный Bullington/P.526 и отдельный P.676 | Для `manual`: $$ propagation\_model\_loss = L_{FS} + L_{diff} $$ $$ propagation\_excess = L_{diff} $$ Для `itm`: $$ L_{total} = L_{FS} + (L_{ITM} - L_{FS}) + L_{veg} + L_{atm} = L_{ITM} + L_{veg} + L_{atm} $$ Для `p452`: $$ L_{total} = L_{FS} + (L_{P.452} - L_{FS}) + L_{veg} = L_{P.452} + L_{veg} $$ Для `manual`: $$ L_{total} = L_{FS} + L_{diff} + L_{veg} + L_{atm} $$ API: `POST /api/v1/link/budget`. ## 5. ITM / Longley-Rice Файл: `api/app/core/itm.py` Библиотека: [`edwardoughton/itmlogic`](https://github.com/edwardoughton/itmlogic). Это Longley-Rice / ITM. В проекте он **не** является реализацией ITU-R P.530. | Параметр | Значение | |----------|----------| | `climate` | 5 по умолчанию | | `ipol` | 0 | | `eps` | 15 | | `sgm` | 0.005 См/м | | `ens` | 314 | | `gma` | $157 \times 10^{-9}$ | | `wn` | $f_{MHz} / 47.7$ | Коды климата: | Среда | `climate` | |-------|-----------| | `urban` | 5 | | `suburban` | 6 | | `rural` | 7 | Профиль PFL содержит DEM-высоты вдоль пути. Высоты концов дополняются AGL: $$ h_0 = h_{DEM,tx} + h_{AGL,tx} $$ $$ h_N = h_{DEM,rx} + h_{AGL,rx} $$ Шаг профиля: $$ \Delta s = \frac{D_{km} \cdot 1000}{N - 1} $$ Итоговая величина в коде: $$ L_{ITM} = 8.685890 \cdot \ln(2 w_n d) + avar(0, 0, 0) $$ Здесь $8.685890 \approx 20 / \ln(10)$, а `avar` — статистическая поправка `itmlogic` для медианного случая. API: `/coverage` (`model=itm`), `/link/budget` (`model=itm`). ## 6. P.1812-подобная упрощённая модель Файл: `api/app/core/itm.py` Это **не полная** ITU-R P.1812. Реализация: $$ L_{p1812\_like} = L_{ITM}(climate) + L_{clutter} $$ | Среда | `climate` | $L_{clutter}$ | |-------|-----------|---------------| | `urban` | 5 | 8 дБ | | `suburban` | 6 | 4 дБ | | `rural` | 7 | 0 дБ | API: `/coverage` (`model=p1812`). ## 7. ITU-R P.452 Файл: `api/app/core/p452.py` Библиотека: [`bwinkel/pycraf`](https://github.com/bwinkel/pycraf). Параметры `PathProp`: | Параметр | Значение | |----------|----------| | Частота | `freq_mhz * u.MHz` | | Температура | 293 K | | Давление | 1013 гПа | | Время | 50% | | Разрешение профиля | $\max(100\text{ м}, 10 \cdot d_{km})$ | | Clutter zone | URBAN / SUBURBAN / SPARSE | Итог: $$ L_{P.452} = loss\_complete(PathProp)[0] $$ Полные уравнения P.452 находятся внутри `pycraf`. API: `/link/budget` (`model=p452`). ## 8. ITU-R P.676 Файл: `api/app/core/atmosphere.py` Библиотека: [`inigodelportillo/ITU-Rpy`](https://github.com/inigodelportillo/ITU-Rpy). $$ f_{GHz} = \frac{f_{Hz}}{10^9} $$ Вызов: ```python itu676.gaseous_attenuation_terrestrial_path( distance_km, freq_ghz, 0, 7.5, 1013, 288, "exact", ) ``` Параметры: высота 0 км, водяной пар 7.5 г/м³, давление 1013 гПа, температура 288 K, метод `exact`. API: `/link/budget` для `manual` и `itm`. Для `p452` отдельный P.676 не добавляется. ## 9. ITU-R P.526: дифракция Файл: `api/app/core/diffraction.py` Параметр Френеля-Кирхгофа: $$ v = h \sqrt{\frac{2(d_1+d_2)}{\lambda d_1 d_2}} $$ Потери острого края: $$ J(v) = \begin{cases} 0, & v \le -0.78 \\ 6.9 + 20\log_{10}\left(\sqrt{(v-0.1)^2 + 1} + v - 0.1\right), & v > -0.78 \end{cases} $$ Bullington-style equivalent edge: $$ h_{path}(d_1) = h_{tx} + (h_{rx}-h_{tx})\frac{d_1}{D} $$ $$ h_i = h_{surface,i} - h_{path}(d_1) $$ $$ L_{diff} = J(\max_i v_i) $$ Как и LOS-модуль, Bullington-style diffraction добавляет выпуклость Земли: $$ h_i = h_{surface,i} + bulge(d_1,d_2,k) - h_{path}(d_1) $$ Функция `deygout(...)` сейчас является compatibility-wrapper над `bullington_loss(...)`; многоэкранный Deygout не реализован. API: `/terrain/los`, `/link/budget` только для `model=manual`. ## 10. Френель и LOS Файл: `api/app/core/fresnel.py` Длина волны: $$ \lambda = \frac{c}{f} $$ Радиус n-й зоны Френеля: $$ r_n = \sqrt{\frac{n \lambda d_1 d_2}{d_1+d_2}} $$ Выпуклость Земли: $$ bulge = \frac{d_1 d_2}{2 k R_e} $$ По умолчанию $k = 1.333$. LOS-анализ: $$ h_{path}(d_1) = h_{tx} + (h_{rx}-h_{tx})\frac{d_1}{D} $$ $$ h_{obstacle} = h_{surface} + bulge(d_1,d_2,k) $$ $$ clearance = h_{path} - h_{obstacle} $$ $$ required = clearance\_fraction \cdot r_1 $$ По умолчанию `clearance_fraction = 0.6`. | Поле | Условие | |------|---------| | `geometric_los` | нет внутренних точек с `clearance < 0` | | `los_clear` | нет внутренних точек с `clearance < required` | | `first_fresnel_clearance_pct` | минимум `clearance / required * 100%` | API: `/terrain/los`, `/terrain/fresnel-slice`, `/link/budget`. ## 11. P.833-подобная растительность Файл: `api/app/core/vegetation.py` Это упрощённая экспоненциальная модель, вдохновлённая P.833, но **не полная ITU-R P.833**, потому что частота в формуле не используется: $$ A_{veg} = A_{max}\left(1 - e^{-(d\gamma)/A_{max}}\right) $$ | Класс WorldCover | $\gamma$, дБ/м | $A_{max}$, дБ | |------------------|----------------|---------------| | `tree_cover` | 0.20 | 30 | | `mangroves` | 0.22 | 32 | | `shrubland` | 0.10 | 12 | | `grassland` | 0.05 | 6 | | `cropland` | 0.04 | 5 | | `herbaceous_wetland` | 0.08 | 10 | | `unknown` | 0.15 | 25 | Глубина растительности: $$ d_{veg} = \sum \Delta s_i $$ Суммируются только сегменты `tree_cover` и `mangroves`. API: `/terrain/los`, `/link/budget`, `/coverage`, `/landcover/path`. ## 12. Диаграммы направленности антенн Файл: `api/app/core/antenna.py` Omni: $$ G(\varphi,\theta) = G_{dBi} $$ Sector: $$ \sigma = \frac{BW}{2.355} $$ $$ normalized = e^{-0.5(\Delta/\sigma)^2} $$ $$ drop = 10(normalized - 1) $$ $$ A = \max(A_{floor}, drop_h + drop_v) $$ Если $|\Delta az| > 90^\circ$: $$ A = \min(A, -A_{F/B}) $$ $$ G = G_{dBi} + A $$ Важно: при такой формуле `beamwidth_h`/`beamwidth_v` **не являются HPBW** в обычном смысле. File pattern: $$ G = G_{dBi} + G_{relative} $$ API: `/antenna/pattern`, `/antenna/beam`, `/coverage`. ## 13. Coverage Файл: `api/app/core/coverage.py` $$ EIRP = P_{tx} + G_{ant}(azimuth, 0^\circ) $$ $$ P_{rx} = EIRP - L_{path} - L_{veg} - L_{surface} + G_{rx} $$ `L_path` выбирается по `model`: FSPL, ITM или p1812-like. Surface obstruction (`L_surface`) считается через Bullington/P.526 по профилю `ground + max(building, canopy)`: - для `fspl`: добавляется полный Bullington loss по surface profile; - для `itm` и `p1812`: добавляется только excess от buildings/canopy поверх terrain-only профиля: $$ L_{surface} = \max(0,\ L_{surface\_profile} - L_{terrain\_profile}) $$ Это снижает риск двойного учёта terrain diffraction, уже входящей в ITM/p1812-like path loss. Алгоритм контура: 1. Для каждого азимута от 0 до 360 градусов. 2. Идти от TX с шагом `range_step_m`. 3. Пока $P_{rx} \ge level\_dbm$, точка считается покрытой. 4. Последняя покрытая точка образует радиальный контур. Raster export: 1. Вокруг TX строится UTM-сетка с пикселем `range_step_m`. 2. Размер bbox: $2R \times 2R$, где $R = radius_m$. 3. Для центра каждой ячейки считается расстояние и азимут от TX. 4. Если расстояние больше `radius_m`, пишется NoData `-9999`. 5. Иначе в ячейку пишется $P_{rx}$ в дБм. GeoTIFF хранит float32 `rx_power_dbm` с CRS/transform. PNG хранит 8-bit preview, масштабированный по min/max конечных значений: $$ png = 1 + \frac{P_{rx} - P_{min}}{P_{max} - P_{min}} \cdot 254 $$ Нулевое значение PNG зарезервировано под NoData. API: `/coverage` → Celery job → `/jobs/{id}`. ## 14. Viewshed Файл: `api/app/core/viewshed.py` Используется GDAL `gdal_viewshed`: 1. DEM-тайлы мозаичатся в UTM. 2. Передаются `-oz`, `-tz`, `-md`. 3. Видимые пиксели (`255`) векторизуются в GeoJSON. `k_factor` из `ViewshedRequest` в `gdal_viewshed` сейчас не передаётся. API: `/viewshed`. ## 15. Здания Файл: `api/app/services/buildings.py` Для точки профиля: $$ h_{building}(point) = \max_{polygon \ni point} height_m $$ Оценка высоты в `db/sql/normalize_buildings.sql`: ```text height_m = OSM tags.height если > 0 иначе levels * 3.0 если levels > 0 иначе по типу: garage/shed -> 3 м industrial/warehouse -> 8 м church -> 12 м default -> 9 м ``` API: `/buildings/query`, косвенно terrain/link. ## 16. Сводная таблица | Модель | Файл | API | |--------|------|-----| | Haversine / WGS84 | `core/geo.py` | почти все расчёты | | DEM sampling | `core/dem.py` | terrain, link, coverage | | Surface profile | `core/surface.py`, `services/terrain.py` | terrain, link | | FSPL | `core/propagation.py` | coverage, link | | ITM / Longley-Rice | `core/itm.py` | coverage, link | | p1812-like | `core/itm.py` | coverage | | P.452 | `core/p452.py` | link | | P.526 / Bullington-style | `core/diffraction.py` | terrain/los, manual link | | P.676 | `core/atmosphere.py` | manual/itm link | | p833-like vegetation | `core/vegetation.py` | terrain/los, link, coverage | | Fresnel / earth bulge | `core/fresnel.py` | terrain/los, fresnel-slice, link | | Antenna | `core/antenna.py` | antenna, coverage | | GDAL viewshed | `core/viewshed.py` | viewshed | ## 17. Эталонные значения из тестов | Расчёт | Вход | Ожидаемый результат | |--------|------|---------------------| | FSPL | $f=433$ МГц, $d=12.45$ км | $107.08$ дБ | | F1 | $f=433$ МГц, $d_1=d_2=6225$ м | $46.45$ м | | Earth bulge | $d_1=d_2=6225$ м, $k=1.333$ | $2.28$ м | | Knife-edge | $v=-0.79$ | $0$ дБ | | Knife-edge | $v=0$ | $6.03$ дБ | | Knife-edge | $v=1$ | $13.93$ дБ | | Vegetation | $d=100$ м, $\gamma=0.2$, $A_{max}=30$ | $14.60$ дБ | ## 18. Ограничения реализации 1. ITM — Longley-Rice через `itmlogic`, не ITU-R P.530. 2. `p1812` в coverage — упрощённая p1812-like модель: ITM + clutter. 3. Vegetation — p833-like модель без частотной зависимости. 4. P.452, ITM и P.676 делегированы внешним библиотекам. 5. `deygout()` оставлен для совместимости, но новый код использует явное `bullington_equivalent_loss()`. 6. `link_viable` зависит только от `fade_margin_db > 0`, не от качества Френеля. 7. Coverage с buildings/canopy требует surface profile на лучах/ячейках и может быть дорогим на мелком `range_step_m`. 8. Viewshed не использует `k_factor`. 9. `diffraction_db` в `/link/budget` оставлен для обратной совместимости; для новой интеграции лучше читать `propagation_excess_db` и `knife_edge_diffraction_db`. ## Связанные документы - [API.md](API.md) - [README.md](README.md) - [SPEC.md](SPEC.md)