Files
RadioPropagationApi/FORMULAS.md
T
2026-06-26 11:21:49 +03:00

39 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 — полная геодезическая длина.

Используется в terrain, LOS, link budget, coverage, landcover.

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 высота кроны, м
surface_m итоговая высота препятствия

Тип препятствия (dominant_obstruction): buildingcanopyterrain.

Примечание: в текущем surface_profile_from_points() растр кроны ещё не подмешивается автоматически в LOS-профиль.

3. FSPL

Файл: api/app/core/propagation.py

Функция: fspl(freq_mhz, dist_km).


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 поле diffraction_db для itm и p452 сейчас фактически содержит model excess над FSPL, потому что отдельного поля propagation_excess_db пока нет.

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

Иначе говоря:


L_{prop,excess} = L_{model} - L_{FS}

Для itm:


L_{total} = L_{FS} + L_{prop,excess} + L_{veg} + L_{atm}
          = L_{ITM} + L_{veg} + L_{atm}

Для p452:


L_{total} = L_{FS} + L_{prop,excess} + 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:

  • POST /api/v1/coverage (model=itm);
  • POST /api/v1/link/budget (model=itm).

6. P.1812-подобная упрощённая модель

Файл: api/app/core/itm.py

Функция: p1812_path_loss(...).

Это не полная 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: POST /api/v1/coverage (model=p1812).

7. ITU-R P.452

Файл: api/app/core/p452.py

Библиотека: bwinkel/pycraf.

Функция: p452_path_loss(...).

Параметры 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. Для /link/budget отдельные Bullington/P.526 и P.676 поверх P.452 не добавляются, чтобы не учитывать те же эффекты дважды.

API: POST /api/v1/link/budget (model=p452).

8. ITU-R P.676

Файл: api/app/core/atmosphere.py

Библиотека: inigodelportillo/ITU-Rpy.

Функция: p676_gas_attenuation(freq_hz, distance_km).


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: POST /api/v1/link/budget (model=manual, model=itm). Для model=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}}

где:

  • h — превышение препятствия над хордой TX-RX;
  • d_1, d_2 — расстояния до препятствия;
  • \lambda = c/f.

Потери острого края


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-модуля. Это физическая несогласованность на длинных пролётах: LOS может стать NLOS из-за earth_bulge, а L_diff останется заниженным.

Функция deygout(...) сейчас является compatibility-wrapper над bullington_loss(...); многоэкранный Deygout не реализован.

API:

  • POST /api/v1/terrain/los;
  • POST /api/v1/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_{tx} = h_{ground,0} + h_{AGL,tx}

h_{rx} = h_{ground,N} + h_{AGL,rx}

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:

  • POST /api/v1/terrain/los;
  • POST /api/v1/terrain/fresnel-slice;
  • POST /api/v1/link/budget.

11. P.833-подобная растительность

Файл: api/app/core/vegetation.py

Функция: p833_attenuation(depth_m, freq_hz, forest_type, ...).

Это упрощённая экспоненциальная модель, вдохновлённая 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

Дефолты из config.py: \gamma = 0.15, A_{max} = 25.

Глубина растительности

Файл: api/app/core/landcover.py


d_{veg} = \sum \Delta s_i

Суммируются только сегменты tree_cover и mangroves.

API:

  • POST /api/v1/terrain/los;
  • POST /api/v1/link/budget;
  • POST /api/v1/coverage при include_vegetation=true;
  • POST /api/v1/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 в обычном смысле. На краю полуширины спад получается около -5 дБ, а не -3 дБ.

File

Файл: api/app/core/antenna_pattern_file.py

Берётся ближайший CSV-сэмпл по az/el:


G = G_{dBi} + G_{relative}

API:

  • POST /api/v1/antenna/pattern;
  • POST /api/v1/antenna/beam;
  • POST /api/v1/coverage.

13. Coverage

Файл: api/app/core/coverage.py

Мощность в точке:


EIRP = P_{tx} + G_{ant}(azimuth, 0^\circ)

P_{rx} = EIRP - L_{path} - L_{veg} + G_{rx}

L_path выбирается по model: FSPL, ITM или p1812-like.

Алгоритм контура:

  1. Для каждого азимута от 0 до 360 градусов.
  2. Идти от TX с шагом range_step_m.
  3. Пока P_{rx} \ge level\_dbm, точка считается покрытой.
  4. Последняя покрытая точка образует радиальный контур.

Для ITM/p1812-like на каждом луче берётся DEM-профиль из 64 точек. Для растительности шаг сэмплирования около 250 м.

API: POST /api/v1/coverage → Celery job → GET /api/v1/jobs/{id}.

14. Viewshed

Файл: api/app/core/viewshed.py

Используется GDAL gdal_viewshed:

  1. DEM-тайлы мозаичатся в UTM.
  2. Передаются -oz (observer AGL), -tz (target AGL), -md (max distance).
  3. Видимые пиксели (255) векторизуются в GeoJSON.

Кастомных RF-формул нет. k_factor из ViewshedRequest в gdal_viewshed сейчас не передаётся.

API: POST /api/v1/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: POST /api/v1/buildings/query, косвенно terrain/link.

16. Сводная таблица

Модель Файл API
Haversine / WGS84 core/geo.py почти все расчёты
DEM sampling core/dem.py terrain, link, coverage
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. Эталонные значения из тестов

Файл: api/tests/test_core.py

Расчёт Вход Ожидаемый результат
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-style equivalent edge.
  6. Дифракция не учитывает earth_bulge, а LOS учитывает.
  7. link_viable зависит только от fade_margin_db > 0, не от качества Френеля.
  8. Coverage строится радиальными контурами, не полноценной 2D-сеткой.
  9. Viewshed не использует k_factor.
  10. В API ответа /link/budget поле diffraction_db для itm/p452 фактически несёт model excess над FSPL; лучше в будущем заменить или дополнить его полем propagation_excess_db.

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

Формулы и расчёты RadioApi

Документ описывает все математические модели, формулы и алгоритмы, реализованные в проекте RadioApi (RF Propagation API).

Единицы по умолчанию:

Величина Единица
Координаты градусы WGS84 (lat, lon)
Расстояние метры (м), километры (км)
Высота метры над уровнем земли (AGL) или над морем (AMSL)
Частота МГц (в API) или Гц (внутри части модулей)
Мощность дБм
Усиление дБи
Потери дБ

Префикс API: /api/v1


Содержание

  1. Геометрия и профиль пути
  2. Профиль поверхности
  3. Свободное пространство (FSPL)
  4. Бюджет радиолинии
  5. ITM / ITU-R P.530
  6. ITU-R P.1812 (упрощённо)
  7. ITU-R P.452
  8. ITU-R P.676 (атмосфера)
  9. ITU-R P.526 (дифракция)
  10. Зоны Френеля и LOS
  11. ITU-R P.833 (растительность)
  12. Диаграммы направленности антенн
  13. Покрытие (coverage)
  14. Viewshed
  15. Здания
  16. Сводная таблица: модель → API
  17. Эталонные значения (тесты)
  18. Ограничения реализации

1. Геометрия и профиль пути

Файл: api/app/core/geo.py

1.1. Расстояние 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}) ]

где:

  • (\varphi) — широта (рад),
  • (\lambda) — долгота (рад),
  • (R_e = 6,371,000) м.

Функция: haversine(a, b) → расстояние в метрах.

1.2. Геодезическая линия WGS84

Функция: sample_path(start, end, n)

  • Строит (n) точек вдоль геодезической линии (эллипсоид WGS84, pyproj.Geod).
  • Каждая точка получает поле distance_m — расстояние от начала пути.
  • Шаг: (d_{step} = D / (n - 1)), где (D) — полная длина геодезической дуги.

Используется в: профиле рельефа, LOS, link budget, coverage, landcover.

1.3. Выборка высот DEM

Файл: api/app/core/dem.py

  • Билинейная интерполяция высоты из GeoTIFF/COG.
  • elevation_at(lat, lon) — одна точка.
  • elevations_along(points) — массив высот вдоль пути.

API: GET /api/v1/terrain/elevation, косвенно все terrain/link/coverage эндпоинты.


2. Профиль поверхности

Файл: api/app/core/surface.py

Для каждой точки пути:

[ h_{surface} = h_{ground} + \max(h_{building}, h_{canopy}) ]

Поле Описание
ground_m Высота рельефа (DEM), м AMSL
building_m Высота здания в точке, м
canopy_m Высота кроны в точке, м
surface_m Итоговая высота препятствия

Тип препятствия (dominant_obstruction): buildingcanopyterrain.

API: POST /api/v1/terrain/profile, /terrain/los, /terrain/fresnel-slice, /link/budget

Примечание: в текущей реализации surface_profile_from_points() не подмешивает растр кроны — canopy_heights остаётся нулевым, если не задано иное.


3. Свободное пространство (FSPL)

Файл: api/app/core/propagation.py
Функция: fspl(freq_mhz, dist_km)

[ L_{FS} = 32.44 + 20 \log_{10}(f_{MHz}) + 20 \log_{10}(d_{km}) \quad [\text{дБ}] ]

Входы:

  • (f_{MHz} > 0) — частота, МГц
  • (d_{km} > 0) — расстояние, км

API:

  • POST /api/v1/coverage (model=fspl)
  • POST /api/v1/link/budget (базовая составляющая)

4. Бюджет радиолинии

Файлы: api/app/core/propagation.py, api/app/services/link.py
Функция: manual_link_budget(...)

4.1. Суммарные потери

[ L_{total} = L_{FS} + L_{diff} + L_{veg} + L_{atm} + L_{misc} + L_{prop,excess} ]

где:

Слагаемое Источник
(L_{FS}) FSPL
(L_{diff}) Дифракция (P.526, Bullington)
(L_{veg}) Растительность (P.833)
(L_{atm}) Атмосфера (P.676)
(L_{misc}) Ручные доп. потери (только model=manual)
(L_{prop,excess}) Избыток над FSPL для ITM/P.452

4.2. Избыток распространения

Для model=itm:

[ L_{prop,excess} = L_{ITM} - L_{FS} ]

Для model=p452:

[ L_{prop,excess} = L_{P.452} - L_{FS} ]

Для model=manual: (L_{prop,excess} = 0).

Дифракция и атмосфера добавляются отдельно, чтобы не дублировать эффекты, уже учтённые в ITM/P.452.

4.3. Мощность на приёме и запас

[ P_{rx} = P_{tx} + G_{tx} + G_{rx} - L_{total} \quad [\text{дБм}] ]

[ \text{margin} = P_{rx} - P_{sens} \quad [\text{дБ}] ]

[ \text{link_viable} = (\text{margin} > 0) ]

API: POST /api/v1/link/budget

Параметры профиля: 256 точек вдоль пути TX→RX.


5. ITM / ITU-R P.530

Файл: api/app/core/itm.py
Библиотека: itmlogic (Longley-Rice)

Функция: itm_path_loss(tx, rx, ...)

5.1. Входные параметры проекта

Параметр Значение по умолчанию
climate 5 (urban)
ipol 0 (вертикальная поляризация)
eps 15 (диэлектрическая проницаемость земли)
sgm 0.005 См/м (проводимость земли)
ens 314
gma (157 \times 10^{-9})
wn (f_{MHz} / 47.7)

5.2. Профиль местности (PFL)

  • 64 точки вдоль геодезической линии (или переданный elevation_profile_m).
  • Высоты концов: (h_0 = h_{DEM,tx} + h_{AGL,tx}), (h_n = h_{DEM,rx} + h_{AGL,rx}).
  • Шаг PFL: (\Delta s = D_{km} \cdot 1000 / (N - 1)) м.

5.3. Итоговые потери

[ L_{ITM} = 8.685890 \cdot \ln(2 \cdot w_n \cdot d) + \text{avar}(z_{0.5}, 0, z_{0.5}) ]

где:

  • (8.685890 \approx 20 / \ln(10)) — перевод натурального логарифма в децибелы,
  • avar — статистическая поправка itmlogic (медиана, квантиль 0.5).

5.4. Коды климата

Среда climate
urban 5
suburban 6
rural 7

API:

  • POST /api/v1/coverage (model=itm)
  • POST /api/v1/link/budget (model=itm)

6. ITU-R P.1812 (упрощённо)

Файл: api/app/core/itm.py
Функция: p1812_path_loss(...)

Это не полная рекомендация ITU-R P.1812, а упрощённая модель:

[ L_{P.1812} = L_{ITM}(climate) + L_{clutter} ]

Среда (environment) climate (L_{clutter}), дБ
urban 5 +8
suburban 6 +4
rural 7 0

API: POST /api/v1/coverage (model=p1812)


7. ITU-R P.452

Файл: api/app/core/p452.py
Библиотека: pycraf

Функция: p452_path_loss(...)

7.1. Параметры PathProp

Параметр Значение
Температура 293 K
Давление 1013 гПа
Время 50%
Разрешение профиля (\max(100\ \text{м},\ 10 \cdot d_{km}))
Зона застройки TX/RX URBAN / SUBURBAN / SPARSE

7.2. Профиль

  • Расстояния hprof_dists — массив в метрах.
  • Высоты hprof_heights — DEM + AGL на концах.
  • Азимут и обратный азимут TX→RX.

7.3. Результат

[ L_{P.452} = \text{loss_complete}(PathProp)[0] ]

Полные уравнения P.452 реализованы внутри pycraf и в проекте не дублируются.

API: POST /api/v1/link/budget (model=p452)


8. ITU-R P.676 (атмосфера)

Файл: api/app/core/atmosphere.py
Библиотека: itur

Функция: p676_gas_attenuation(freq_hz, distance_km)

[ L_{atm} = A_{gas}(d_{km},\ f_{GHz},\ h_1,\ \rho_{H_2O},\ P,\ T) ]

Параметры атмосферы (фиксированные в проекте):

Параметр Значение
(h_1) 0 км
(\rho_{H_2O}) 7.5 г/м³
(P) 1013 гПа
(T) 288 K
Метод "exact"

[ f_{GHz} = f_{Hz} / 10^9 ]

API: POST /api/v1/link/budget → поле atmospheric_db


9. ITU-R P.526 (дифракция)

Файл: api/app/core/diffraction.py

9.1. Параметр Френеля-Кирхгофа

Функция: knife_edge_v(h, d1, d2, freq_hz)

[ v = h \sqrt{\frac{2(d_1 + d_2)}{\lambda , d_1 , d_2}} ]

где:

  • (h) — превышение препятствия над хордой TX–RX, м
  • (d_1, d_2) — расстояния до препятствия от TX и RX, м
  • (\lambda = c / f) — длина волны, м
  • (c = 299,792,458) м/с

9.2. Потери острого края J(v)

Функция: knife_edge_loss(v) — ITU-R P.526:

[ 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} ]

9.3. Bullington (эквивалентный край)

Функция: bullington_loss(profile, ...)

Для каждой внутренней точки профиля ((i = 1 \ldots N-2)):

[ h_{path}(d_1) = h_{tx} + (h_{rx} - h_{tx}) \frac{d_1}{D} ]

[ h_i = h_{surface,i} - h_{path}(d_1) ]

[ v_i = \text{knife_edge_v}(h_i, d_1, d_2, f) ]

[ L_{diff} = J!\left(\max_i v_i\right) ]

Земная кривизна в дифракции не учитывается (только в LOS-анализе).

9.4. Deygout

Функция: deygout(...) — обёртка над bullington_loss() (многоэкранная Deygout не реализована).

API:

  • POST /api/v1/terrain/losdiffraction_loss_db
  • POST /api/v1/link/budgetdiffraction_db

10. Зоны Френеля и LOS

Файл: api/app/core/fresnel.py

10.1. Длина волны

[ \lambda = \frac{c}{f} ]

10.2. Радиус n-й зоны Френеля

Функция: fresnel_radius(λ, d1, d2, n=1)

[ r_n = \sqrt{\frac{n \lambda d_1 d_2}{d_1 + d_2}} ]

10.3. Выпуклость Земли (earth bulge)

Функция: earth_bulge(d1, d2, k)

[ \text{bulge} = \frac{d_1 d_2}{2 k R_e} ]

По умолчанию (k = 1.333) (эффективный радиус Земли для радиоволн).

10.4. LOS-анализ

Функция: los_analysis(profile, tx_height_agl, rx_height_agl, freq_hz, clearance=0.6, k=1.333)

Для каждой точки профиля:

[ h_{tx} = h_{ground,0} + h_{AGL,tx} ]

[ h_{rx} = h_{ground,N} + h_{AGL,rx} ]

[ h_{path}(d_1) = h_{tx} + (h_{rx} - h_{tx}) \frac{d_1}{D} ]

[ h_{obstacle} = h_{surface} + \text{bulge}(d_1, d_2, k) ]

[ \text{clearance}m = h{path} - h_{obstacle} ]

[ r_{F1} = \text{fresnel_radius}(\lambda, d_1, d_2, n=1) ]

[ \text{required} = \text{clearance_fraction} \cdot r_{F1} ]

По умолчанию clearance_fraction = 0.6 (60% первой зоны Френеля должно быть свободно).

10.5. Критерии

Поле Условие
geometric_los нет точек с clearance_m < 0
los_clear нет точек с clearance_m < required
first_fresnel_clearance_pct (\min\left(\dfrac{\text{clearance}_m}{\text{required}} \times 100%\right))

10.6. Fresnel slice (GeoJSON)

Файл: api/app/services/terrain.py

Строит 3D-полигон зоны Френеля вдоль пути:

  • Верх/низ: (h_{path} \pm \text{required})
  • Буфер радиусом (r_{F1} \times \text{fresnel_fraction}) (мин. 0.1 м) в UTM

API:

  • POST /api/v1/terrain/los
  • POST /api/v1/terrain/fresnel-slice
  • POST /api/v1/link/budget (fresnel_clear, worst_obstruction, …)

11. ITU-R P.833 (растительность)

Файл: api/app/core/vegetation.py
Функция: p833_attenuation(depth_m, freq_hz, forest_type, ...)

Упрощённая экспоненциальная модель:

[ A_{veg} = A_{max} \left(1 - e^{-(d \cdot \gamma) / A_{max}}\right) \quad [\text{дБ}] ]

где:

  • (d) — глубина прохождения через растительность, м
  • (\gamma) — коэффициент затухания, дБ/м
  • (A_{max}) — предельное затухание, дБ

Параметр freq_hz проверяется на положительность, но в формулу не входит.

11.1. Коэффициенты по классам 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

Дефолты из .env / config.py: (\gamma = 0.15), (A_{max} = 25).

11.2. Глубина растительности

Файл: api/app/core/landcover.py

[ d_{veg} = \sum \Delta s_i \quad \text{для сегментов с классом tree_cover или mangroves} ]

Доминирующий класс для P.833 — класс с наибольшей суммарной длиной сегментов вдоль пути.

API:

  • POST /api/v1/terrain/losvegetation_loss_db
  • POST /api/v1/link/budgetvegetation_db
  • POST /api/v1/coverage (include_vegetation=true)
  • POST /api/v1/landcover/path (сегменты без P.833)

12. Диаграммы направленности антенн

Файл: api/app/core/antenna.py

12.1. Omni

[ G(\varphi, \theta) = G_{dBi} ]

12.2. Sector (гауссовы лепестки)

[ \sigma = \frac{BW}{2.355} ]

[ \text{normalized} = e^{-0.5 \cdot (\Delta / \sigma)^2} ]

[ \text{drop} = 10 \cdot (\text{normalized} - 1) \quad [\text{дБ}] ]

[ A = \max(A_{floor},\ \text{drop}_h + \text{drop}_v) ]

Если (|\Delta az| > 90°):

[ A = \min(A,\ -A_{F/B}) ]

[ G = G_{dBi} + A ]

Параметр По умолчанию
front_to_back_db 25 дБ
sidelobe_floor_db 30 дБ

12.3. File (CSV)

Файл: api/app/core/antenna_pattern_file.py

Ближайший сэмпл из файла (az, el):

[ G = G_{dBi} + G_{relative} ]

API:

  • POST /api/v1/antenna/pattern
  • POST /api/v1/antenna/beam
  • POST /api/v1/coverage (EIRP)

13. Покрытие (coverage)

Файл: api/app/core/coverage.py

13.1. Мощность на приёме в точке

[ P_{rx} = \text{EIRP} - L_{path} - L_{veg} + G_{rx} \quad [\text{дБм}] ]

[ \text{EIRP} = P_{tx} + G_{ant}(\text{azimuth}, 0°) ]

(L_{path}) — FSPL, ITM или P.1812 в зависимости от model.

13.2. Радиальный контур

Для каждого азимута (\varphi = 0, \Delta\varphi, 2\Delta\varphi, \ldots, 360°):

  1. Увеличивать расстояние (d) с шагом range_step_m от TX.
  2. Пока (P_{rx}(d, \varphi) \ge \text{level_dbm}) — точка входит в контур.
  3. Последняя «хорошая» точка добавляется в полигон.

Для ITM/P.1812: DEM-профиль из 64 точек на каждом радиальном луче.

Для растительности: sample_path с шагом ~250 м.

13.3. Выход

GeoJSON Polygon для каждого уровня из levels_dbm.

API: POST /api/v1/coverage → Celery job → GET /api/v1/jobs/{id}

Поддерживаемые модели: fspl, itm, p1812 (P.452 в coverage не используется).


14. Viewshed

Файл: api/app/core/viewshed.py

Алгоритм GDAL gdal_viewshed:

  1. Мозаика DEM-тайлов в UTM по bbox радиуса.
  2. Запуск: -oz (высота наблюдателя AGL), -tz (высота цели AGL), -md (макс. дистанция).
  3. Видимые пиксели = 255 → векторизация в GeoJSON.

Кастомных RF-формул нет — стандартный алгоритм линии визирования GDAL.
Параметр k_factor из запроса в GDAL не передаётся.

API: POST /api/v1/viewshed → Celery job


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:

  • POST /api/v1/buildings/query (геометрия и высоты)
  • косвенно: terrain, los, fresnel-slice, link budget

16. Сводная таблица: модель → API

Стандарт / модель Файл Эндпоинты
Haversine / WGS84 core/geo.py все
DEM sampling core/dem.py /terrain/*, link, coverage
FSPL core/propagation.py /coverage, /link/budget
ITM (P.530) core/itm.py /coverage, /link/budget
P.1812 (упрощ.) core/itm.py /coverage
P.452 core/p452.py /link/budget
P.526 core/diffraction.py /terrain/los, /link/budget
P.676 core/atmosphere.py /link/budget
P.833 core/vegetation.py /terrain/los, /link/budget, /coverage
Fresnel / earth bulge core/fresnel.py /terrain/los, /terrain/fresnel-slice, /link/budget
Sector antenna core/antenna.py /antenna/*, /coverage
GDAL viewshed core/viewshed.py /viewshed
Landcover core/landcover.py /landcover/path

17. Эталонные значения (тесты)

Из api/tests/test_core.py:

Расчёт Вход Ожидаемый результат
FSPL (f = 433) МГц, (d = 12.45) км (L_{FS} \approx 107.08) дБ
F1 radius (f = 433) МГц, (d_1 = d_2 = 6225) м (r_1 \approx 46.45) м
Earth bulge (d_1 = d_2 = 6225) м, (k = 1.333) (\approx 2.28) м
Knife-edge J(v) (v = -0.79) 0 дБ
Knife-edge J(v) (v = 0) 6.03 дБ
Knife-edge J(v) (v = 1) 13.93 дБ
P.833 (d = 100) м, (\gamma = 0.2), (A_{max} = 30) (\approx 14.60) дБ

18. Ограничения реализации

  1. P.1812 — не полная рекомендация ITU-R, а ITM + clutter по типу среды.
  2. P.452, ITM, P.676 — делегированы внешним библиотекам (pycraf, itmlogic, itur).
  3. P.833 — упрощённая модель без зависимости от частоты.
  4. Canopy в LOS — инфраструктура есть, но растр кроны в профиль поверхности пока не подмешивается автоматически.
  5. Deygout — фактически Bullington single-edge.
  6. link_viable зависит только от fade_margin_db > 0, не от Fresnel quality.
  7. Coverage — радиальные контуры, не полноценная 2D-решётка.
  8. Viewshedk_factor из запроса не используется в GDAL.
  9. Дифракция — без учёта земной кривизны (в отличие от LOS).

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

  • API.md — описание эндпоинтов и параметров запросов
  • README.md — установка, данные, bootstrap-скрипты
  • SPEC.md — спецификация проекта