Máscaras de horizonte e geometria do céu
Até a versão 0,36, todo teste de visibilidade da Terra no motor era um único corte de elevação, mask_deg: um satélite contava quando estava no valor ou acima de um número, qualquer que fosse sua direção. Uma rua, um cais, um fiorde ou um guindaste bloqueia parte do céu e deixa o restante aberto. Um skyline (linha do horizonte) é um corte de elevação que depende do azimute; as funções de geometria do céu transformam um local, um instante, constelações e um skyline no conjunto visível, na diluição da precisão (DOP), na covariância da geometria, na elipse de erro horizontal, em uma nuvem de Monte Carlo e em um skyplot. O projeto é docs/design/2026-10-12-geometry-and-skyline-design.md (itens 1 e 2).
Evidência: a decisão de visibilidade com máscara (ângulos de visada geodésicos, a máscara em tabela, o conjunto visível e seu DOP) é VALIDATED em relação ao Orekit 12,2 e ao gnss_lib_py 1.0.4 em uma comparação pré-registrada, e o mesmo vale para a elipse de erro e o cofator por sistema (numpy, pré-registrado); as reduções de canyon, de setor e de terreno, as predefinições e o restante das funções de geometria do céu são MODELLED (Evidência). Um skyline remove satélites em linha de visada e nada além disso. As predefinições são geometrias REPRESENTATIVE, não levantamentos.
O skyline#
Um skyline é o máximo ponto a ponto de quatro descrições. O azimute é medido em graus, no sentido horário a partir do norte verdadeiro, no referencial local leste-norte-cima do local; as elevações estão em [0, 90).
| Descrição | TOML | O que é |
|---|---|---|
| Tabela | table = [[az, el], ...] |
Elevações interpoladas linearmente em azimute, com retorno em 360°. Azimutes estritamente crescentes em [0, 360); no máximo 3600 linhas. |
| Canyon urbano | street_az_deg, width_m, height_left_m, height_right_m, antenna_m, length_m |
Duas paredes retas e paralelas (abaixo). |
| Setores | sectors = [{ az_from, az_to, el_deg }] |
Intervalos de bordas nítidas, no sentido horário de az_from (incluído) até az_to (excluído), que podem passar pelo norte; no máximo 360. |
| Terreno | terrain = [{ az, distance_m, height_m }] |
Pontos reduzidos a uma tabela: elevação atan((height_m − d²/(2 R_eff)) / d), com a queda por curvatura para R_eff = 4/3 × 6371 km (uma constante escolhida e declarada). height_m é a altura acima da antena. |
preset = "<name>" parte de uma geometria nomeada; os campos do canyon então substituem, campo a campo, o canyon da predefinição, e table, sectors e terrain se somam a ele.
O mask_deg do receptor mantém seu significado: é um skyline plano. O corte efetivo no azimute az é max(mask_deg, skyline(az)), e um satélite é visível quando sua elevação está no valor ou acima dele: o mesmo >= que a máscara plana sempre usou. Um satélite acima de mask_deg, mas abaixo do skyline, é mascarado pelo skyline.
O canyon urbano#
Uma rua segue ao longo do azimute s, com w metros entre as paredes, e a antena a a metros de altura sobre o eixo central. Ao longo do azimute az, com θ = az − s, o raio atinge uma parede após d(az) = (w/2) / |sin θ|; assim, uma parede de altura h corta em
cut(az) = atan( (h − a) / d(az) ) (the right wall for sin θ > 0, the side at s + 90°)
A faixa aberta fica ao longo da rua. Com length_m = L (a antena no meio), um raio sai por uma extremidade aberta, e a parede não corta nada, quando d(az) · |cos θ| > L/2. Uma parede que não seja mais alta que a antena não corta nada. antenna_m tem como padrão 1,5 m (o teto de um carro ou um receptor portátil, uma escolha declarada); um novo canyon precisa de width_m.
Predefinições#
Dimensões escolhidas e declaradas (as da nota de projeto), não levantamentos de nenhum lugar real. kshana skyline --preset <name> imprime uma delas com sua proveniência e seu corte a cada grau.
| Predefinição | Geometria | Proveniência |
|---|---|---|
city-street |
Canyon: rua a 0°, largura de 20 m, paredes de 30 m e 30 m, antena a 1,5 m. 70,7° na direção transversal à rua, aberto ao longo dela. | Relação altura/largura de 1,5, dentro da faixa de 0,5–2 de razões de aspecto de canyons urbanos em Oke (1988), Street design and urban canopy layer climate, Energy and Buildings 11, 103–113. |
harbour-approach |
Setores: aberto para o mar ao longo de 090–270 (180°); edifícios do cais a 8° ao longo de 315–045 (90°); o quebra-mar e seu farol a 3° ao longo de 045–090 e de 270–315. | Escolhas declaradas (por exemplo, armazéns de 40 m a cerca de 285 m). |
fjord |
Terreno: paredes 600 m acima da antena, a 1500 m de cada lado de um canal norte-sul, um ponto por grau (a 1500/|sin az| m), com a queda por curvatura aplicada. 21,8° na direção transversal, abaixo de 1° ao longo do eixo. |
A ordem de grandeza da largura e do relevo dos profundos fiordes do oeste da Noruega (Holtedahl (1967), Notes on the formation of fjords and fjord-valleys, Geografiska Annaler 49A, 193–203). |
container-terminal |
Setores: pilhas de contêineres a 12° ao longo da metade voltada para terra 000–180; lanças de guindaste a 35° ao longo de 245–255 e a 25° ao longo de 285–295; aberto para a água nos demais pontos. | Cinco contêineres high-cube ISO 668 (14,5 m) a cerca de 60 m de uma antena a 2 m de altura; uma lança a cerca de 50 m acima do cais, a 70 m e 100 m, da ordem de um guindaste post-Panamax. Um setor de guindaste é opaco. |
Em um cenário#
Uma seção [skyline] opcional, sobre o corte próprio do tipo:
| Tipo | O que o skyline altera | Por amostra |
|---|---|---|
orbit (com user_site) |
A linha do tempo de visibilidade (disponibilidade de GNSS) e o resumo do DOP. | — |
integrity (com user_site) |
Os satélites que o RAIM e o ARAIM (araim_dual) usam. |
epochs[].masked_by_skyline |
gnss-sim |
Os satélites cujas pseudodistâncias são simuladas e usadas pelo RAIM. | gnss_measurements[].masked_by_skyline |
jamming |
Os satélites que o receptor consegue rastrear, antes do jammer. | epochs[].masked_by_skyline |
leo-pvt |
O conjunto em visibilidade de cada modo sobre a máscara própria de cada sistema, inclusive a varredura polar (o mesmo skyline em todos os locais). O envelope do modo Doppler e a passagem única projetada são limites do que um receptor deve tratar e mantêm a máscara de cada sistema. | joint.epochs[].masked_by_skyline |
leo-pass |
Quais épocas contam como acima da máscara, o tempo acima dela e os satélites da comparação de GNSS. | satellites[].pass.epochs_masked_by_skyline |
nmea-scenario (NMEA de treinamento) |
Os satélites em GSV e na solução de posição. Os azimutes são rumos verdadeiros fixos em relação ao solo, não o rumo da embarcação. | — |
O local em solo de orbit e integrity. Ambos os tipos modelam um usuário em órbita com elevação geocêntrica e sem azimute fixo à Terra; por isso, só aceitam um skyline com o modo de local em solo da nota de projeto: user_site = { lat_deg, lon_deg, height_m } no lugar de user (exatamente um dos dois). Cada amostra é um instante absoluto, epoch + t: todo satélite é propagado até ele (um conjunto de elementos, uma efeméride de transmissão ou um arquivo SP3 avança a partir de sua própria época; um satélite Walker conta t a partir de epoch) e girado para o referencial fixo à Terra nesse instante, de modo que o local vê um céu consistente. epoch é a do próprio cenário de órbita; sem uma (e sempre para integrity, que não tem nenhuma), é a época mais recente entre os satélites que possuem uma, ou J2000 apenas para padrões Walker. A elevação e o azimute são geodésicos (WGS-84). A divisão horizontal e vertical dos níveis de proteção ARAIM do tipo de integridade e do erro vertical de Stanford usa o mesmo "cima" geodésico (a normal ao elipsoide WGS-84, cerca de 0,19° em relação à radial geocêntrica a 45° de latitude); o RAIM instantâneo já o usava. Um user_site sem skyline é um usuário comum em solo; o caminho do user em órbita permanece inalterado.
kind = "integrity"
mask_deg = 5.0
# ...
[user_site]
lat_deg = 59.45
lon_deg = 24.75
height_m = 15.0
[skyline]
preset = "harbour-approach"
Compatibilidade retroativa. Sem uma seção [skyline], um cenário é idêntico byte a byte ao que o mesmo motor produzia antes de existir o código de skyline: tests/skyline_scenarios.rs confere o JSON, o gráfico e o resumo de todo cenário distribuído que executa um destes tipos, e o texto NMEA de todo cenário de treinamento, em relação a resumos SHA-256 da linha de base "0,36 portable" (os resumos do commit da versão 0.35.0, refeitos quando o motor passou para a matemática portátil e mantidos no teste como registro: docs/revisions/STUDIO15-portable-math-rebaseline.md). Uma seção [skyline] vazia não corta nada; o resultado então é igual ao da execução sem ela, exceto pelo bloco skyline acrescentado e pelas contagens masked_by_skyline iguais a zero.
O que é recusado, com o motivo na mensagem: um skyline em um user em órbita; user e user_site juntos; uma predefinição desconhecida; um canyon sem width_m; uma tabela que não é estritamente crescente em [0, 360); uma elevação fora de [0, 90); um setor vazio; uma distância de terreno não positiva; entrada acima dos limites; qualquer chave desconhecida.
O resultado ganha skyline: { source, open_fraction, provenance } quando a seção está presente: source é preset:<name>, preset:<name>+overrides ou custom; open_fraction é a fração do ângulo sólido do hemisfério superior acima de max(mask_deg, skyline) (a média de 1 − sin(cut) sobre o azimute: 1 para uma máscara zero, 0,5 para um corte plano de 30°); provenance é a linha da predefinição ou a do próprio cenário. As unidades e as classes de proveniência estão no bloco units.
Exemplos incluídos (kshana example <name>):
| Exemplo | O que ele mostra (remova a seção [skyline] para comparar) |
|---|---|
orbit-urban-canyon-skyline |
GPS + Galileo no fundo de city-street: 17 de 289 amostras mantêm uma solução de posição, PDOP mediano de 70,5 (1,14 em céu aberto). |
integrity-harbour-approach |
ARAIM de dupla constelação entrando em um porto: 70 satélites-época ocultos, disponibilidade inalterada (145 de 145). |
gnss-sim-container-terminal-skyline |
Pseudodistâncias e RAIM em um cais: 44 satélites-época ocultos; disponibilidade de RAIM de 0,59 (0,85 em céu aberto). |
jamming-harbour-approach-skyline |
Um jammer de 2 W em um porto: 83 satélites-época ocultos, rastreamento inalterado (o jammer já os havia tomado). |
leo-pvt-fjord-skyline |
GPS + Galileo + uma camada LEO em um fiorde: PDOP fundido mediano de 1,14 (0,93 em céu aberto). |
leo-vertical-autonomous-vehicle-skyline |
O canyon de leo-vertical-autonomous-vehicle como um corte plano de 35° e como city-street girado ao longo do rumo do carro: 8 contra 4 a 5 satélites GPS e Galileo; erro médio fundido de 2,1 m em ambos; o alarme de 1,5 m em 51 % e 59 % das épocas. |
Geometria do céu#
Cinco funções do motor (src/geometry.rs), as mesmas em todas as superfícies:
| Função | Entrada → saída |
|---|---|
sky_geometry(request) |
por satélite 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 (variância unitária), covariance_enu_m2; ellipse_1sigma e ellipse na confiança solicitada; label, statement, advisory |
sky_monte_carlo(request, seed, n) |
n (no máximo 5000, um limite de entrada) erros de mínimos quadrados sob erros de distância gaussianos independentes de σ_UERE a partir de um fluxo ChaCha8 inicializado por seed: samples_enu_m, a covariância empírica, o RMS horizontal e 3-D ao lado de σ_UERE × HDOP e σ_UERE × PDOP, a fração dentro da elipse de 95 % |
skyline_presets() |
as predefinições com rótulo, descrição, proveniência, o corte a cada grau e open_fraction |
skyline_profile(skyline) |
uma seção de skyline reduzida à tabela de 360 entradas que o motor usa, com source, provenance, open_fraction |
skyplot_svg(request) |
o skyplot como SVG estático (sem script, sem referência externa, com toda string de entrada escapada) |
Uma requisição:
{
"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 são as predefinições de constelação do motor (gps-baseline, galileo, beidou-meo, glonass etc. de constellation-design; as predefinições LEO xona-pulsar, iridium-stl etc.) ou uma tabela completa leo-pvt [[system]], propagadas até time_s (kepleriana com J2 secular, as órbitas do modo conjunto leo-pvt). Suas fases relativas vêm de épocas de referência diferentes; portanto, o céu é uma geometria representativa, não o céu daquela data naquele lugar. Os satélites podem, em vez disso (ou além disso), ser dados diretamente: "satellites": [{"id": "G05", "system": "GPS", "az_deg": 120, "el_deg": 35}, ...], ou com "ecef_m": [x, y, z] e um site ou user_ecef_m. Padrões: a máscara própria de cada sistema (5° para GNSS, 10° para LEO; 5° para satélites dados diretamente), um relógio de receptor por sistema ("common" para um relógio compartilhado), σ_UERE de 1 m (uma linha de orçamento representativa de dupla frequência), confiança de 0,95. Limites de entrada: no máximo 2000 satélites acima do horizonte (uma tabela de sistema que peça mais é recusada antes de ser construída) e 16 relógios de receptor.
Um céu datado a partir de conjuntos de elementos. "tle" recebe texto de conjuntos de elementos de duas linhas (uma linha de nome antes de cada conjunto é opcional; no máximo 4 MiB, um limite de entrada), propagados pelo SGP4 do motor (WGS-72, o propagador que orbit-real-tle usa) até "epoch" mais time_s, girados para o referencial fixo à Terra com UT1 tomado como UTC e sem movimento polar, e vistos de site. "epoch" é UTC ISO 8601 (2026-06-07T12:00:00Z) e data apenas os conjuntos de elementos (as predefinições não têm data; portanto, um epoch sem tle é recusado); "tle_system" os rotula para o relógio por sistema (padrão TLE). Todo resultado informa qual céu é em sky_source e sky_statement:
sky_source |
O céu |
|---|---|
presets |
as predefinições de constelação do motor: uma geometria representativa |
tle-at-epoch |
os conjuntos de elementos propagados até a época declarada: o céu que esses conjuntos de elementos dão naquela época, não uma medição dele (a precisão própria deles é a do SGP4 e a da idade dos conjuntos) |
tle-at-element-epoch |
sem epoch: os conjuntos de elementos propagados até a época própria do conjunto mais recente mais time_s, um céu representativo |
satellites |
satélites dados diretamente, como declarados |
Uma mistura os une com +. epoch_utc é o instante até o qual os conjuntos de elementos foram propagados, e tle_skipped indica o que do texto não foi usado, com o motivo: linhas de conjuntos de elementos malformadas ou órfãs (com seus números de linha), conjuntos que o analisador recusou e conjuntos que o SGP4 não conseguiu propagar até aquele instante (decaídos ou além de seus limites); no máximo 100 entradas no total, e então uma última entrada contando o restante. Uma lista systems vazia não adiciona predefinições. O skyplot declara a base em seu rodapé.
Um único núcleo. A matriz de cofatores é orbit::cofactor_from_los, a montagem da matriz de projeto e a inversa de Gauss-Jordan por trás de orbit::dop. orbit::dop_cov expõe a covariância e orbit::dop é sua redução; assim, os escalares VALIDATED permanecem inalterados e continuam fixados por tests/dop_reference.rs. Não existe uma segunda implementação de DOP.
A elipse. Semieixos k σ √λ para os autovalores λ do bloco leste-norte, com k = √(−2 ln(1 − p)) (√5.991 = 2.448 a 95 %, 1 a 39,35 %), e o azimute do eixo maior ½ atan2(2 Q_EN, Q_NN − Q_EE) em [0, 180) a partir do norte.
| Superfície | Pontos de entrada |
|---|---|
| CLI | kshana sky <request.json|toml> ou kshana sky --site <lat,lon[,h]> (--systems <a,b> | --tle <file>) [--epoch] [--tle-system] [--time] [--mask] [--skyline-preset] [--uere] [--clock], com --monte-carlo <n> --seed <s> ou --svg; kshana skyline --preset <name> | --presets | --profile <file> |
| Rust | kshana::geometry::{sky_geometry, sky_monte_carlo, skyline_presets, skyline_profile, skyplot_svg}, kshana::surface::* com os mesmos nomes |
| 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, ou skyline_preset); sem Monte Carlo via MCP (amostras em grande volume que um agente deve resumir, não transportar) |
Todo resultado traz label (MODELLED), o statement (geometria sob um modelo de erro declarado: erros de distância gaussianos, independentes, iguais e de média zero; não uma previsão do erro real de um receptor, que também tem vieses, multipercurso e erros sem linha de visada; não um limite de integridade) e o aviso consultivo: Software de caráter consultivo, não um equipamento de navegação homologado (IEC 61108, IEC 61162): o operador continua responsável pela navegação da embarcação.
Evidências#
Cinco linhas do registro (docs/VERIFICATION-MATRIX.md), como a nota de projeto as delimita:
| Linha | Status | Referência independente |
|---|---|---|
| Máscara de horizonte dependente do azimute: conjunto visível, azimute e elevação, e DOP sob a máscara | VALIDATED | Orekit 12,2 (ângulos de visada de TopocentricFrame, ElevationMask) com gnss_lib_py 1.0.4 (get_dop e a matriz de DOP), pré-registrados em xval/orekit-skyline/PREREGISTRATION.md (commit e4b9d912) antes de qualquer um dos dois ser executado: 1173 ângulos de visada em seis locais e quatro épocas, seis tabelas, 142 conjuntos visíveis; pior elevação de 1.3e-13°, azimute de 2.3e-13°, máscara de 1.1e-12° contra 1e-9°; conjuntos visíveis idênticos, nenhum na faixa de fronteira; DOP de 3.3e-13 relativo e cofator de 6.6e-13 da escala da matriz contra 1e-9. Escopo: máscaras em tabela, ângulos de visada geodésicos, o conjunto mascarado e seu DOP e cofator de relógio comum. |
| Reduções de cânion urbano, setor e terreno a uma tabela | MODELLED | valores calculados à mão; a comparação acima lê suas tabelas como entradas e não as verifica |
| Predefinições de skyline | MODELLED | nenhuma: dimensões escolhidas e declaradas (verificadas apenas em relação a si mesmas) |
| Elipse de erro horizontal e cofator por sistema a partir da covariância da geometria | VALIDATED | numpy 2.5.4 linalg.inv e linalg.eigh (P2 padrão) sobre os 142 conjuntos visíveis versionados, relógio comum e um relógio por sistema, pré-registrados em xval/numpy-ellipse/PREREGISTRATION.md (commit 0bbeeaee): 270 casos, eixos de 5.1e-13 relativo, orientação de 5.3e-12°, cofator de 1.0e-12 da escala da matriz, contra 1e-9 e 1e-6° |
| Funções de geometria do céu | MODELLED | a composição: propagação das predefinições até um céu representativo, a nuvem de Monte Carlo (verificada em relação à geometria que ela amostra), as superfícies |
A ausência de uma seção [skyline] é verificada byte a byte em relação às saídas da versão 0.35.0, o que mostra compatibilidade retroativa e não valida nada. O gnss_lib_py aplica apenas uma única máscara de elevação; por isso, é a referência independente de DOP aqui, não a referência independente de máscara.
O que um skyline não modela: difração na borda de uma parede, reflexos e sinais sem linha de visada que um receptor real ainda pode rastrear (eles são descartados, não degradados), transparência parcial (um guindaste de treliça é opaco em todo o seu setor), terreno além dos pontos dados e a curvatura da Terra dentro das formas de canyon e de setor.