Files
RadioPropagationApi/FORMULAS.md
T
2026-06-26 12:23:46 +03:00

16 KiB
Raw Blame History

Формулы и расчёты 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): buildingcanopyterrain.

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.

Это 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.

Параметры 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.


f_{GHz} = \frac{f_{Hz}}{10^9}

Вызов:

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:

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.

Связанные документы