Маски горизонту та геометрія неба
До 0,36 кожна перевірка видимості над Землею в рушії була єдиним порогом висоти, mask_deg: супутник враховувався, якщо перебував на цій одній висоті або вище, незалежно від напрямку. Вулиця, причал, фіорд чи кран затуляють частину неба й залишають решту відкритою. Лінія горизонту (skyline) — це поріг висоти, що залежить від азимута; функції геометрії неба перетворюють місце, час, орбітальні угруповання та лінію горизонту на видимий набір, геометричний чинник DOP (погіршення точності), коваріацію геометрії, горизонтальний еліпс похибок, хмару Монте-Карло та скайплот. Проєктний документ — docs/design/2026-10-12-geometry-and-skyline-design.md (пункти 1 і 2).
Докази: рішення щодо маскованої видимості (геодезичні кути спостереження, табличну маску, видимий набір і його DOP) VALIDATED за Orekit 12,2 і gnss_lib_py 1.0.4 у попередньо зареєстрованому порівнянні, так само й еліпс похибок та коефіцієнт для кожної системи (numpy, попередня реєстрація); зведення каньйону, секторів і рельєфу, пресети та решта функцій геометрії неба MODELLED (Докази). Лінія горизонту прибирає супутники прямої видимості й нічого більше. Пресети — це REPRESENTATIVE геометрії, а не результати зйомки місцевості.
Лінія горизонту#
Лінія горизонту — це поточкове максимум чотирьох описів. Азимут — це градуси за годинниковою стрілкою від істинної півночі в локальній системі координат «схід–північ–вгору» місця спостереження; висоти задано в [0, 90).
| Опис | TOML | Що це |
|---|---|---|
| Таблиця | table = [[az, el], ...] |
Висоти лінійно інтерполюються за азимутом із замиканням на 360°. Азимути строго зростають у межах [0, 360); не більше 3600 рядків. |
| Вуличний каньйон | street_az_deg, width_m, height_left_m, height_right_m, antenna_m, length_m |
Дві прямі паралельні стіни (див. нижче). |
| Сектори | sectors = [{ az_from, az_to, el_deg }] |
Інтервали з різкими краями за годинниковою стрілкою від az_from (включно) до az_to (не включно), які можуть переходити через північ; не більше 360. |
| Рельєф | terrain = [{ az, distance_m, height_m }] |
Точки, зведені до таблиці: висота atan((height_m − d²/(2 R_eff)) / d) із поправкою на кривизну для R_eff = 4/3 × 6371 km (вибрана й наведена стала). height_m відлічується над антеною. |
preset = "<name>" починає з іменованої геометрії; поля каньйону потім перевизначають поля каньйону пресета по одному, а table, sectors і terrain доповнюють його.
Параметр mask_deg приймача зберігає своє значення: це пласка лінія горизонту. Ефективний поріг на азимуті az дорівнює max(mask_deg, skyline(az)), і супутник видимий, коли його висота не менша за нього: це той самий >=, що завжди використовувала пласка маска. Супутник над mask_deg, але під лінією горизонту, замаскований лінією горизонту.
Вуличний каньйон#
Вулиця проходить уздовж азимута s, між стінами w метрів, антена на висоті a метрів на осьовій лінії. Уздовж азимута az при θ = az − s промінь досягає стіни після d(az) = (w/2) / |sin θ|, тож стіна висотою h відсікає на
cut(az) = atan( (h − a) / d(az) ) (the right wall for sin θ > 0, the side at s + 90°)
Відкрита смуга лежить уздовж вулиці. При length_m = L (антена посередині) промінь виходить через відкритий кінець, і стіна нічого не відсікає, коли d(az) · |cos θ| > L/2. Стіна не вища за антену нічого не відсікає. antenna_m за замовчуванням дорівнює 1,5 m (дах автомобіля або переносний приймач, вибране значення); новий каньйон потребує width_m.
Пресети#
Вибрані й наведені розміри (з проєктної нотатки), а не результати зйомки жодного реального місця. kshana skyline --preset <name> виводить один із них разом із походженням і порогом на кожен градус.
| Передналаштування | Геометрія | Походження |
|---|---|---|
city-street |
Каньйон: вулиця 0°, ширина 20 m, стіни 30 m і 30 m, антена 1,5 m. 70,7° упоперек вулиці, відкрито вздовж неї. | Відношення висоти до ширини 1,5, у межах діапазону 0,5–2 співвідношень сторін вуличних каньйонів за Oke (1988), Street design and urban canopy layer climate, Energy and Buildings 11, 103–113. |
harbour-approach |
Сектори: відкрито в бік моря на 090–270 (180°); будівлі набережної 8° на 315–045 (90°); хвилеріз і його маяк 3° на 045–090 та 270–315. | Вибрані значення (наприклад, склади 40 m приблизно за 285 m). |
fjord |
Рельєф: стіни на 600 m над антеною на відстані 1500 m з обох боків від каналу північ–південь, одна точка на градус (за 1500/|sin az| м), з поправкою на кривизну. 21,8° упоперек, нижче 1° уздовж осі. |
Порядок ширини та рельєфу глибоких західнонорвезьких фіордів (Holtedahl (1967), Notes on the formation of fjords and fjord-valleys, Geografiska Annaler 49A, 193–203). |
container-terminal |
Сектори: штабелі контейнерів 12° над сухопутною половиною 000–180; стріли кранів 35° на 245–255 і 25° на 285–295; в інших напрямках відкрито до води. | П'ять контейнерів ISO 668 High Cube (14,5 m) приблизно за 60 m від антени на висоті 2 m; стріла приблизно на 50 m над причалом на відстані 70 m і 100 m, порядок величини крана типу post-Panamax. Сектор крана непрозорий. |
У сценарії#
Необов'язковий розділ [skyline] діє поверх власного порогу відповідного типу:
| Вид | Що змінює лінія горизонту | На кожен відлік |
|---|---|---|
orbit (з user_site) |
Часова шкала видимості (доступність GNSS) і підсумок DOP. | — |
integrity (з user_site) |
Супутники, які використовують RAIM і ARAIM (araim_dual). |
epochs[].masked_by_skyline |
gnss-sim |
Супутники, псевдодальності яких моделюються й використовуються в RAIM. | gnss_measurements[].masked_by_skyline |
jamming |
Супутники, які приймач може відстежувати, до впливу глушника. | epochs[].masked_by_skyline |
leo-pvt |
Набір супутників у зоні видимості для кожного режиму над маскою власної системи, включно зі полярним розгортанням (та сама лінія горизонту на кожному місці). Огинаюча режиму Доплера та спроєктований одиночний прохід є межами того, що має обробляти приймач, і зберігають маску кожної системи. | joint.epochs[].masked_by_skyline |
leo-pass |
Які епохи вважаються такими, що вище маски, час над нею, супутники для порівняння GNSS. | satellites[].pass.epochs_masked_by_skyline |
nmea-scenario (навчальний NMEA) |
Супутники в GSV і в розв'язку. Азимути — це істинні пеленги, прив'язані до землі, а не курс судна. | — |
Наземне місце для orbit і integrity. Обидва типи моделюють користувача на орбіті з геоцентричною висотою і без азимута в земній системі координат, тому лінію горизонту вони приймають лише в режимі наземного місця з проєктної нотатки: user_site = { lat_deg, lon_deg, height_m } замість user (рівно одне з двох). Кожен відлік — це один абсолютний момент, epoch + t: кожен супутник пропагується до нього (набір елементів, бортові ефемериди або файл SP3 просуваються від власної епохи, супутник Walker відлічує t від epoch) і повертається в земну систему координат на цей момент, тож місце бачить одне узгоджене небо. epoch — це власна епоха орбітального сценарію; без неї (і завжди для integrity, який її не має) це найновіша епоха серед супутників, що її мають, або J2000 лише для візерунків Walker. Висота й азимут геодезичні (WGS-84). Поділ на горизонтальну та вертикальну складові рівнів захисту ARAIM типу integrity і вертикальної похибки за Stanford використовує ту саму геодезичну вертикаль (нормаль до еліпсоїда WGS-84, приблизно на 0,19° від геоцентричного радіуса на широті 45°); миттєвий RAIM її вже використовував. user_site без лінії горизонту — це звичайний наземний користувач; шлях орбітального user не змінюється.
kind = "integrity"
mask_deg = 5.0
# ...
[user_site]
lat_deg = 59.45
lon_deg = 24.75
height_m = 15.0
[skyline]
preset = "harbour-approach"
Зворотна сумісність. Без розділу [skyline] сценарій побайтово збігається з тим, що давав той самий рушій до появи коду лінії горизонту: tests/skyline_scenarios.rs перевіряє JSON, діаграму та підсумок кожного постачуваного сценарію, що запускає один із цих типів, а також текст NMEA кожного навчального сценарію за дайджестами SHA-256 базового рівня «0,36 portable» (дайджести комміту випуску 0.35.0, перебазовані, коли рушій перейшов на портативну математику, і збережені в тесті як запис: docs/revisions/STUDIO15-portable-math-rebaseline.md). Порожній розділ [skyline] нічого не відсікає; тоді результат дорівнює прогону без нього, окрім доданого блока skyline і нульових лічильників masked_by_skyline.
Що відхиляється, з причиною в повідомленні: лінія горизонту для орбітального user; user і user_site разом; невідомий пресет; каньйон без width_m; таблиця, не строго зростаюча в [0, 360); висота поза [0, 90); порожній сектор; невласна (недодатна) відстань рельєфу; вхідні дані понад обмеження; будь-який невідомий ключ.
Результат отримує skyline: { source, open_fraction, provenance }, коли розділ присутній: source дорівнює preset:<name>, preset:<name>+overrides або custom; open_fraction — це частка тілесного кута верхньої півсфери над max(mask_deg, skyline) (середнє 1 − sin(cut) за азимутом: 1 для нульової маски, 0,5 для пласкої межі 30°); provenance — це рядок пресета або власний рядок сценарію. Одиниці та класи походження наведено в блоці units.
Вбудовані приклади (kshana example <name>):
| Приклад | Що він показує (приберіть розділ [skyline], щоб порівняти) |
|---|---|
orbit-urban-canyon-skyline |
GPS + Galileo на дні city-street: 17 із 289 відліків зберігають розв'язок, медіанний PDOP 70,5 (1,14 під відкритим небом). |
integrity-harbour-approach |
ARAIM для двох орбітальних угруповань під час заходу в порт: 70 епох супутників приховано, доступність без змін (145 із 145). |
gnss-sim-container-terminal-skyline |
Псевдодальності та RAIM на причалі: 44 епохи супутників приховано; доступність RAIM 0,59 (0,85 під відкритим небом). |
jamming-harbour-approach-skyline |
Глушник 2 W у гавані: 83 епохи супутників приховано, відстеження без змін (їх уже захопив глушник). |
leo-pvt-fjord-skyline |
GPS + Galileo + шар LEO у фіорді: медіанний об'єднаний PDOP 1,14 (0,93 під відкритим небом). |
leo-vertical-autonomous-vehicle-skyline |
Каньйон leo-vertical-autonomous-vehicle як пласка межа 35° і як city-street, розвернутий уздовж курсу автомобіля: 8 проти від 4 до 5 супутників GPS і Galileo; об'єднана середня похибка 2,1 m в обох випадках; сигнал тривоги 1,5 m у 51 % і 59 % епох. |
Геометрія неба#
П'ять функцій рушія (src/geometry.rs), однакових на кожній поверхні:
| Функція | Вхід → вихід |
|---|---|
sky_geometry(request) |
для кожного супутника id, system, az_deg, el_deg, cut_deg, visible, masked; n_visible, n_masked, open_fraction; dop (GDOP, PDOP, HDOP, VDOP, TDOP, tdop_by_clock); states, cofactor_enu (одинична дисперсія), covariance_enu_m2; ellipse_1sigma і ellipse із запитаною довірчою ймовірністю; label, statement, advisory |
sky_monte_carlo(request, seed, n) |
n (не більше 5000, обмеження вхідних даних) похибок методу найменших квадратів за незалежних гауссових похибок дальності σ_UERE з потоку ChaCha8, ініціалізованого seed: samples_enu_m, емпірична коваріація, горизонтальне й 3D RMS поруч із σ_UERE × HDOP і σ_UERE × PDOP, частка всередині еліпса 95 % |
skyline_presets() |
пресети з міткою, описом, походженням, порогом на кожен градус і open_fraction |
skyline_profile(skyline) |
розділ лінії горизонту, зведений до таблиці з 360 записів, яку використовує рушій, з source, provenance, open_fraction |
skyplot_svg(request) |
скайплот як статичний SVG (без скриптів, без зовнішніх посилань, кожен вхідний рядок екранований) |
Запит:
{
"site": {"lat_deg": 59.91, "lon_deg": 10.75, "height_m": 5},
"time_s": 3600,
"systems": ["gps-baseline", "galileo", "beidou-meo", "xona-pulsar"],
"mask_deg": 5,
"skyline": {"preset": "fjord"},
"clock": "per-system",
"sigma_uere_m": 1.5,
"confidence": 0.95
}
systems — це пресети орбітальних угруповань рушія (constellation-design: gps-baseline, galileo, beidou-meo, glonass, ...; пресети LEO xona-pulsar, iridium-stl, ...) або повна таблиця leo-pvt [[system]], пропагована до time_s (кеплерова з віковим J2, орбіти спільного режиму leo-pvt). Їхні відносні фази походять від різних опорних епох, тож небо — це репрезентативна геометрія, а не небо тієї дати в тому місці. Супутники можна натомість (або додатково) задати безпосередньо: "satellites": [{"id": "G05", "system": "GPS", "az_deg": 120, "el_deg": 35}, ...], або з "ecef_m": [x, y, z] і site чи user_ecef_m. Типові значення: маска кожної системи (5° для GNSS, 10° для LEO; 5° для супутників, заданих безпосередньо), один годинник приймача на систему ("common" для одного спільного годинника), σ_UERE 1 m (репрезентативний рядок бюджету для двох частот), довірча ймовірність 0,95. Обмеження вхідних даних: не більше 2000 супутників над горизонтом (системна таблиця, що запитує більше, відхиляється до її побудови) і 16 годинників приймача.
Датоване небо за наборами елементів. "tle" приймає текст двострокових наборів елементів (рядок з назвою перед кожним набором необов'язковий; не більше 4 MiB, обмеження вхідних даних), пропагований SGP4 рушія (WGS-72, пропагатор, який використовує orbit-real-tle) до "epoch" плюс time_s, повернутий у земну систему координат із UT1, прийнятим за UTC, і без полярного руху, та побачений із site. "epoch" — це UTC за ISO 8601 (2026-06-07T12:00:00Z), і воно датує лише набори елементів (пресети дати не мають, тому epoch без tle відхиляється); "tle_system" позначає їх для годинника кожної системи (типово TLE). Кожен результат вказує, яке саме небо в sky_source і sky_statement:
sky_source |
Небо |
|---|---|
presets |
пресети орбітальних угруповань рушія: репрезентативна геометрія |
tle-at-epoch |
набори елементів, пропаговані до вказаної епохи: небо, яке ці набори дають на ту епоху, а не його вимірювання (власна точність обмежена SGP4 і віком наборів) |
tle-at-element-epoch |
без epoch: набори елементів, пропаговані до власної епохи найновішого набору плюс time_s, репрезентативне небо |
satellites |
супутники, задані безпосередньо, як указано |
Суміш поєднує їх за допомогою +. epoch_utc — це момент, до якого пропаговано набори елементів, а tle_skipped називає, яку частину тексту не використано, з причиною: спотворені або осиротілі рядки наборів елементів (з їхніми номерами), набори, які парсер відхилив, і набори, які SGP4 не зміг пропагувати до цього моменту (зійшли з орбіти або вийшли за свої межі); усього не більше 100 записів, після чого один останній запис підраховує решту. Порожній список systems не додає пресетів. Скайплот зазначає підставу в нижньому колонтитулі.
Одне ядро. Матриця кофакторів — це orbit::cofactor_from_los, збирання матриці плану та обернення Гаусса–Жордана, що лежать в основі orbit::dop. orbit::dop_cov надає коваріацію, а orbit::dop — її згортку, тож скаляри VALIDATED не змінилися й досі зафіксовані в tests/dop_reference.rs. Другої реалізації DOP немає.
Еліпс. Півосі k σ √λ для власних значень λ блока схід–північ із k = √(−2 ln(1 − p)) (√5.991 = 2.448 при 95 %, 1 при 39,35 %) та азимут великої осі ½ atan2(2 Q_EN, Q_NN − Q_EE) у [0, 180) від півночі.
| Поверхня | Точки входу |
|---|---|
| CLI | kshana sky <request.json|toml> або kshana sky --site <lat,lon[,h]> (--systems <a,b> | --tle <file>) [--epoch] [--tle-system] [--time] [--mask] [--skyline-preset] [--uere] [--clock], з --monte-carlo <n> --seed <s> або --svg; kshana skyline --preset <name> | --presets | --profile <file> |
| Rust | kshana::geometry::{sky_geometry, sky_monte_carlo, skyline_presets, skyline_profile, skyplot_svg}, kshana::surface::* з тими самими назвами |
| Python | kshana.sky_geometry, sky_monte_carlo, skyline_presets, skyline_profile, skyplot_svg |
| WASM | sky_geometry(request_json), sky_monte_carlo(request_json, seed, n), skyline_presets(), skyline_profile(skyline_json), skyplot_svg(request_json) |
| MCP | sky_geometry (request або skyline_preset); через MCP Монте-Карло немає (масові вибірки, які агент має підсумовувати, а не переносити) |
Кожен результат містить label (MODELLED), statement (геометрія за заданої моделі похибок: незалежні, однакові гауссові похибки дальності з нульовим середнім; це не прогноз реальної похибки приймача, яка містить також зміщення, багатопроменевість і похибки сигналів без прямої видимості; це не межа цілісності) та консультативну примітку: Консультативне програмне забезпечення, не навігаційне обладнання із затвердженням типу (IEC 61108, IEC 61162): відповідальність за навігацію судна залишається на операторі.
Докази#
П'ять рядків реєстру (docs/VERIFICATION-MATRIX.md), як їх окреслює проєктна нотатка:
| Рядок | Статус | Оракул |
|---|---|---|
| Азимутально залежна маска горизонту: видима множина, азимут і висота та DOP під маскою | VALIDATED | Orekit 12,2 (кути спостереження TopocentricFrame, ElevationMask) разом із gnss_lib_py 1.0.4 (get_dop і матриця DOP), попередньо зареєстровано в xval/orekit-skyline/PREREGISTRATION.md (коміт e4b9d912) до запуску кожного з них: 1173 кутів спостереження на шести місцях і чотирьох епохах, шість таблиць, 142 видимих наборів; найгірша висота 1.3e-13°, азимут 2.3e-13°, маска 1.1e-12° проти 1e-9°; видимі набори ідентичні, жоден не потрапив у прикордонну смугу; DOP 3.3e-13 відносно, а кофактор 6.6e-13 масштабу матриці проти 1e-9. Обсяг: табличні маски, геодезичні кути спостереження, маскований набір, його DOP зі спільним годинником і кофактор. |
| Зведення міського каньйону, секторів і рельєфу до таблиці | MODELLED | значення, обчислені вручну; наведене вище порівняння читає їхні таблиці як вхідні дані й не перевіряє їх |
| Пресети лінії горизонту | MODELLED | немає: вибрані й наведені розміри (перевірені лише самі щодо себе) |
| Горизонтальний еліпс похибки та кофактор для кожної системи з коваріації геометрії | VALIDATED | numpy 2.5.4 linalg.inv і linalg.eigh (стандартний P2) на 142 закомічених видимих наборах, зі спільним годинником і з одним годинником на систему, попередньо зареєстровано в xval/numpy-ellipse/PREREGISTRATION.md (коміт 0bbeeaee): 270 випадків, осі 5.1e-13 відносно, орієнтація 5.3e-12°, кофактор 1.0e-12 масштабу матриці проти 1e-9 і 1e-6° |
| Функції геометрії неба | MODELLED | композиція: пропагація пресета до репрезентативного неба, хмара Монте-Карло (звірена з геометрією, яку вона вибірково відтворює), поверхні |
Відсутність розділу [skyline] перевіряється побайтово щодо вихідних даних 0.35.0, що показує зворотну сумісність і нічого не підтверджує за зовнішнім еталоном. gnss_lib_py застосовує лише одну маску висоти, тож тут він є оракулом для DOP, а не для маски.
Чого лінія горизонту не моделює: дифракції на краю стіни, відбиттів і сигналів без прямої видимості, які реальний приймач усе ще може відстежувати (їх відкидають, а не послаблюють), часткової прозорості (ґратчастий кран непрозорий у межах свого сектора), рельєфу за межами заданих точок і кривизни Землі всередині форм каньйону та сектора.