39 KiB
Формулы и расчёты 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): building → canopy → terrain.
Примечание: в текущем 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.
Алгоритм контура:
- Для каждого азимута от 0 до 360 градусов.
- Идти от TX с шагом
range_step_m. - Пока
P_{rx} \ge level\_dbm, точка считается покрытой. - Последняя покрытая точка образует радиальный контур.
Для 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:
- DEM-тайлы мозаичатся в UTM.
- Передаются
-oz(observer AGL),-tz(target AGL),-md(max distance). - Видимые пиксели (
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. Ограничения реализации
- ITM — Longley-Rice через
itmlogic, не ITU-R P.530. p1812в coverage — упрощённая p1812-like модель: ITM + clutter.- Vegetation — p833-like модель без частотной зависимости.
- P.452, ITM и P.676 делегированы внешним библиотекам.
deygout()по имени не соответствует реализации: сейчас это Bullington-style equivalent edge.- Дифракция не учитывает
earth_bulge, а LOS учитывает. link_viableзависит только отfade_margin_db > 0, не от качества Френеля.- Coverage строится радиальными контурами, не полноценной 2D-сеткой.
- Viewshed не использует
k_factor. - В 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
Содержание
- Геометрия и профиль пути
- Профиль поверхности
- Свободное пространство (FSPL)
- Бюджет радиолинии
- ITM / ITU-R P.530
- ITU-R P.1812 (упрощённо)
- ITU-R P.452
- ITU-R P.676 (атмосфера)
- ITU-R P.526 (дифракция)
- Зоны Френеля и LOS
- ITU-R P.833 (растительность)
- Диаграммы направленности антенн
- Покрытие (coverage)
- Viewshed
- Здания
- Сводная таблица: модель → API
- Эталонные значения (тесты)
- Ограничения реализации
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): building → canopy → terrain.
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/los→diffraction_loss_dbPOST /api/v1/link/budget→diffraction_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/losPOST /api/v1/terrain/fresnel-slicePOST /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/los→vegetation_loss_dbPOST /api/v1/link/budget→vegetation_dbPOST /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/patternPOST /api/v1/antenna/beamPOST /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°):
- Увеличивать расстояние (d) с шагом
range_step_mот TX. - Пока (P_{rx}(d, \varphi) \ge \text{level_dbm}) — точка входит в контур.
- Последняя «хорошая» точка добавляется в полигон.
Для 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:
- Мозаика DEM-тайлов в UTM по bbox радиуса.
- Запуск:
-oz(высота наблюдателя AGL),-tz(высота цели AGL),-md(макс. дистанция). - Видимые пиксели = 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. Ограничения реализации
- P.1812 — не полная рекомендация ITU-R, а ITM + clutter по типу среды.
- P.452, ITM, P.676 — делегированы внешним библиотекам (
pycraf,itmlogic,itur). - P.833 — упрощённая модель без зависимости от частоты.
- Canopy в LOS — инфраструктура есть, но растр кроны в профиль поверхности пока не подмешивается автоматически.
- Deygout — фактически Bullington single-edge.
link_viableзависит только отfade_margin_db > 0, не от Fresnel quality.- Coverage — радиальные контуры, не полноценная 2D-решётка.
- Viewshed —
k_factorиз запроса не используется в GDAL. - Дифракция — без учёта земной кривизны (в отличие от LOS).