Маски горизонта и геометрия неба
До 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 м (крыша автомобиля или ручной приёмник — заданный выбор); для нового каньона нужен width_m.
Предустановки#
Выбранные и заданные размеры (из проектной записки), а не съёмки какого-либо реального места. kshana skyline --preset <name> выводит одну из них с её происхождением и срезом через каждый градус.
| Пресет | Геометрия | Происхождение |
|---|---|---|
city-street |
Каньон: улица 0°, ширина 20 м, стены 30 м и 30 м, антенна 1,5 м. 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 м примерно в 285 м). |
fjord |
Рельеф: стены на 600 м выше антенны на расстоянии 1500 м по обе стороны от канала с севера на юг, одна точка на градус (на 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 повышенной вместимости (14,5 м) примерно в 60 м от антенны на высоте 2 м; стрела примерно на 50 м выше причала на расстоянии 70 м и 100 м, порядка крана класса 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 и вертикальной ошибки Стэнфорда использует то же геодезическое «вверх» (нормаль к эллипсоиду 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 Вт в гавани: скрыто 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 м в обоих случаях; сигнал тревоги при 1,5 м в 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, эмпирическая ковариация, горизонтальная и 3-D СКО рядом с σ_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 — это предустановки орбитальных группировок движка (gps-baseline, galileo, beidou-meo, glonass, ... из constellation-design; предустановки 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 м (репрезентативная строка бюджета для двух частот), доверительная вероятность 0,95. Ограничения ввода: не более 2000 спутников над горизонтом (системная таблица, запрашивающая больше, отклоняется до построения) и 16 часов приёмника.
Небо на заданную дату по наборам элементов. "tle" принимает текст двухстрочных наборов элементов (строка с названием перед каждым набором необязательна; не более 4 МиБ, ограничение ввода), распространяемых средствами 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, а не оракулом для маски.
Чего линия горизонта не моделирует: дифракцию на кромке стены, отражения и сигналы вне прямой видимости, которые реальный приёмник всё ещё может сопровождать (они отбрасываются, а не ухудшаются), частичную прозрачность (решётчатый кран непрозрачен во всём своём секторе), рельеф за пределами заданных точек и кривизну Земли внутри форм «каньон» и «сектор».