16 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 — полная геодезическая длина.
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.
Это 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.
Алгоритм контура:
- Для каждого азимута от 0 до 360 градусов.
- Идти от TX с шагом
range_step_m. - Пока
P_{rx} \ge level\_dbm, точка считается покрытой. - Последняя покрытая точка образует радиальный контур.
Raster export:
- Вокруг TX строится UTM-сетка с пикселем
range_step_m. - Размер bbox:
2R \times 2R, гдеR = radius_m. - Для центра каждой ячейки считается расстояние и азимут от TX.
- Если расстояние больше
radius_m, пишется NoData-9999. - Иначе в ячейку пишется
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:
- DEM-тайлы мозаичатся в UTM.
- Передаются
-oz,-tz,-md. - Видимые пиксели (
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. Ограничения реализации
- ITM — Longley-Rice через
itmlogic, не ITU-R P.530. p1812в coverage — упрощённая p1812-like модель: ITM + clutter.- Vegetation — p833-like модель без частотной зависимости.
- P.452, ITM и P.676 делегированы внешним библиотекам.
deygout()оставлен для совместимости, но новый код использует явноеbullington_equivalent_loss().link_viableзависит только отfade_margin_db > 0, не от качества Френеля.- Coverage с buildings/canopy требует surface profile на лучах/ячейках и может быть дорогим на мелком
range_step_m. - Viewshed не использует
k_factor. diffraction_dbв/link/budgetоставлен для обратной совместимости; для новой интеграции лучше читатьpropagation_excess_dbиknife_edge_diffraction_db.