Skip to content

Real-time station generation and coordinate format export selector#48

Merged
Geovannisz merged 22 commits intomainfrom
copilot/enhance-oskar-frontend
Feb 11, 2026
Merged

Real-time station generation and coordinate format export selector#48
Geovannisz merged 22 commits intomainfrom
copilot/enhance-oskar-frontend

Conversation

Copy link
Contributor

Copilot AI commented Feb 10, 2026

Station parameter changes required manual button clicks to regenerate layouts. Export section showed all three coordinate formats (WGS84/ECEF/ENU) simultaneously without explaining their differences.

Real-time station generation

  • Added debounced (150ms) _autoGenerate() in StationManager that fires generateStations() on any parameter change
  • Listeners bound to station-count, station-spacing, station-layout-type, and all dynamically-created extra params via _updateExtraParams()
  • Existing "Gerar Stations" and "Randomizar" buttons preserved for explicit control
  • Initial generation on page load

Coordinate format selector

WGS84, ECEF, and ENU are three distinct coordinate systems — not interchangeable:

Format Description OSKAR use
WGS84 Geodetic (lat, lon, alt) Primary station positions
ECEF Geocentric Cartesian (X,Y,Z from Earth center) layout_ecef.txt for absolute positioning
ENU Local topocentric (East, North, Up) layout_enu.txt for relative positioning
  • Radio selector switches visible export textarea between ECEF and ENU; WGS84 always shown as primary
  • ℹ️ help tooltips on each option explain the coordinate system and its simulation purpose
  • Both formats still computed internally for ZIP export regardless of selection

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
…ator, and UV coverage modules

Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
…cument optional components

Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
Copilot AI changed the title [WIP] Enhance frontend for OSKAR with advanced user features Add tab-based UI with station manager, OSKAR INI generator, sky model builder, and UV coverage visualization Feb 10, 2026
Copilot AI requested a review from Geovannisz February 10, 2026 21:46
…, and enhanced station parameters

Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
… sizing

Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
Copilot AI changed the title Map distance toggle, INI [General] header, sky-INI bundling, file browser buttons, complete OSKAR parameters Fix UV chart positioning, file selector popup, OS-aware line endings, and add oskar_imager INI block Feb 11, 2026
…erometer link, add beam pattern INI

Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
…r beam pattern

Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
Copilot AI changed the title Fix UV chart positioning, file selector popup, OS-aware line endings, and add oskar_imager INI block Complete OSKAR frontend: tab system, station generator, INI configs, sky model, UV coverage Feb 11, 2026
…V text, add WebGPU, add Ctrl+scroll zoom

Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
…ove CSS, add paste feedback, fix redundancies

Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
Copilot AI changed the title Complete OSKAR frontend: tab system, station generator, INI configs, sky model, UV coverage Enhanced station layouts, OSKAR config generators, UV WebGPU acceleration, and UX improvements Feb 11, 2026
…ormat selector (ECEF/ENU)

Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
…ms listener binding

Co-authored-by: Geovannisz <82838501+Geovannisz@users.noreply.github.com>
Copilot AI changed the title Enhanced station layouts, OSKAR config generators, UV WebGPU acceleration, and UX improvements Real-time station generation and coordinate format export selector Feb 11, 2026
@Geovannisz Geovannisz marked this pull request as ready for review February 11, 2026 04:14
Copilot AI review requested due to automatic review settings February 11, 2026 04:14
@Geovannisz Geovannisz merged commit e8766e6 into main Feb 11, 2026
3 checks passed
@Geovannisz Geovannisz deleted the copilot/enhance-oskar-frontend branch February 11, 2026 04:14
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Esta PR reorganiza a UI em abas e adiciona novos módulos para melhorar o fluxo de trabalho: geração de stations em tempo real (com debounce), exportação com seletor de formato de coordenadas (ECEF/ENU) e uma nova aba de simulação/plot de cobertura UV.

Changes:

  • Implementa navegação por abas (Layout / OSKAR / Sky Model / Cobertura UV) e barra de atalhos rápidos.
  • Adiciona geração automática (debounced) de layouts de stations e integração com exportação/mapa.
  • Introduz seletor de formato alternativo de coordenadas (ECEF/ENU) na exportação e adiciona módulo de cobertura UV (com fallback CPU e tentativa WebGPU).

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
js/uv_coverage.js Novo simulador/plotador de cobertura UV (CPU + tentativa de WebGPU), exportação de imagem e atualização via evento.
js/tabs.js Novo gerenciador de abas com evento tabChanged e navegação por teclado.
js/stations.js Novo StationManager com geração automática debounced e novos tipos de layout/params dinâmicos.
js/sky_model.js Novo gerador de sky model (modo simples/avançado) com preview, tabela e download/cópia.
js/map.js Ajustes no mapa (Ctrl+scroll zoom) e layer dedicada para linhas de distância.
js/main.js Integração com TabManager, atalhos rápidos e resize de plots ao trocar de aba.
js/export.js Seletor ECEF/ENU na UI, geração de layout_ecef.txt e layout_enu.txt, inclusão opcional no ZIP.
index.html Reestruturação grande da UI para abas + novas seções (Sky Model, UV, stations) e seletor de coordenadas.
css/styles.css Estilos para abas, layout 3-colunas (stations/map/info), barra de atalhos, seletor ECEF/ENU e UV plot.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (enuPanel) enuPanel.style.display = radio.value === 'enu' ? '' : 'none';
});
});

Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O seletor de formato (ECEF/ENU) só atualiza os painéis em eventos change. Se o navegador restaurar o radio selecionado (ex.: refresh com autofill) ou se o estado inicial não for 'ecef', a UI pode abrir mostrando o painel errado. Sugestão: após registrar os listeners, executar uma atualização inicial baseada em document.querySelector('input[name="coord-format"]:checked').

Suggested change
// Atualiza inicialmente os painéis com base no radio atualmente selecionado.
const initiallySelectedCoordRadio = document.querySelector('input[name="coord-format"]:checked');
if (initiallySelectedCoordRadio) {
if (ecefPanel) ecefPanel.style.display = initiallySelectedCoordRadio.value === 'ecef' ? '' : 'none';
if (enuPanel) enuPanel.style.display = initiallySelectedCoordRadio.value === 'enu' ? '' : 'none';
}

Copilot uses AI. Check for mistakes.
Comment on lines +972 to +997
calculateAngularResolution() {
if (!this.angularResDisplay) return;

if (this.maxBaseline <= 0) {
this.angularResDisplay.textContent = 'Resolução angular: N/A (sem baselines)';
return;
}

const thetaRad = STATION_WAVELENGTH / this.maxBaseline;
const thetaDeg = thetaRad * STATION_RAD_TO_DEG;
const thetaArcmin = thetaDeg * 60;
const thetaArcsec = thetaArcmin * 60;

let display;
if (thetaArcmin >= 1) {
display = `${thetaArcmin.toFixed(2)} arcmin`;
} else {
display = `${thetaArcsec.toFixed(2)} arcsec`;
}

this.angularResDisplay.innerHTML =
`<strong>Resolução Angular (θ ≈ λ/D<sub>max</sub>):</strong><br>` +
`θ = ${display}<br>` +
`λ = ${STATION_WAVELENGTH.toFixed(4)} m | D<sub>max</sub> = ${this._formatDistance(this.maxBaseline)}<br>` +
`f = ${(STATION_FREQUENCY_HZ / 1e9).toFixed(3)} GHz`;
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calculateAngularResolution() sobrescreve todo o conteúdo de #angular-resolution-display via innerHTML, mas o HTML novo já define elementos específicos (#resolution-arcmin, #resolution-arcsec) para exibição e estilização. Isso remove a UI/estilos previstos e deixa os spans do layout sem uso. Sugestão: preencher #resolution-arcmin/#resolution-arcsec e manter o restante do painel intacto.

Copilot uses AI. Check for mistakes.
Comment on lines +1015 to +1034
updateStats() {
if (!this.statsContainer) return;

if (this.stations.length === 0) {
this.statsContainer.textContent = 'Nenhuma estação gerada.';
return;
}

let maxR = 0;
for (const s of this.stations) {
const r = Math.sqrt(s.x * s.x + s.y * s.y);
if (r > maxR) maxR = r;
}

this.statsContainer.innerHTML =
`<strong>Estações:</strong> ${this.stations.length}<br>` +
`Baselines: ${this.baselines.length} pares<br>` +
`Raio máximo: ${this._formatDistance(maxR)}<br>` +
`Baseline max: ${this._formatDistance(this.maxBaseline)}`;
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateStats() sobrescreve #station-stats com innerHTML, porém o HTML novo desse painel já contém uma grade de estatísticas com spans (#stat-station-count, #stat-baseline-count). Com a implementação atual, esses spans nunca são atualizados e a estrutura/estilo do card é perdida. Sugestão: atualizar os spans por ID e evitar substituir o markup do container.

Copilot uses AI. Check for mistakes.
Comment on lines +567 to +590
calculateResolution() {
if (!this.uvData || this.uvData.maxBaseline <= 0) {
if (this.resolutionDisplay) {
this.resolutionDisplay.textContent = 'Resolução: N/A';
}
return;
}

const { maxBaseline, lambda } = this.uvData;
const thetaRad = lambda / maxBaseline;
const thetaDeg = thetaRad * BingoConstants.RAD_TO_DEG;
const thetaArcmin = thetaDeg * 60;
const thetaArcsec = thetaArcmin * 60;

let displayText;
if (thetaArcmin >= 1) {
displayText = `Resolução angular: ${thetaArcmin.toFixed(2)} arcmin (θ ≈ λ/D_max)`;
} else {
displayText = `Resolução angular: ${thetaArcsec.toFixed(2)} arcsec (θ ≈ λ/D_max)`;
}

if (this.resolutionDisplay) {
this.resolutionDisplay.textContent = displayText;
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calculateResolution() escreve em this.resolutionDisplay (apontando para #uv-resolution-display) via textContent, o que apaga o <h3> e o conteúdo interno do box. No HTML o texto da resolução fica em #uv-resolution-text; seria melhor guardar a referência a esse elemento e atualizar só ele (ex.: innerText/textContent do #uv-resolution-text).

Copilot uses AI. Check for mistakes.
Comment on lines +178 to +233
async generateUVCoverage() {
const stations = this.getStationPositions();
if (!stations || stations.length < 2) {
this.updateStatus("Erro: São necessárias pelo menos 2 estações para calcular a cobertura UV.");
console.error("UVCoverageSimulator: Número insuficiente de estações.");
return;
}

this.updateStatus("Calculando cobertura UV...");

// Aguarda WebGPU ficar pronto (se ainda estiver inicializando)
if (this._gpuReadyPromise) {
await this._gpuReadyPromise;
}

const params = this._readParams();
const decRad = params.dec * BingoConstants.DEG_TO_RAD;
const lambda = BingoConstants.SPEED_OF_LIGHT / params.freqHz;
const sinDec = Math.sin(decRad);
const cosDec = Math.cos(decRad);

// Ângulos horários: centrados em 0, de -duration/2 a +duration/2
const halfDuration = params.duration / 2;
const hourAngles = [];
for (let i = 0; i < params.timesteps; i++) {
const hHours = params.timesteps === 1 ? 0 : -halfDuration + (params.duration * i) / (params.timesteps - 1);
hourAngles.push(hHours * 15 * BingoConstants.DEG_TO_RAD);
}

let uvResult;

// Tenta usar WebGPU para cálculos massivos
if (this.gpuAvailable && this.gpuDevice && stations.length >= GPU_MIN_STATIONS_THRESHOLD) {
try {
this.updateStatus("Calculando cobertura UV (WebGPU)...");
uvResult = await this._computeUVonGPU(stations, hourAngles, sinDec, cosDec, lambda);
this.updateStatus(`Cobertura UV gerada via GPU: ${uvResult.nBaselines} baselines, ${uvResult.uPoints.length} pontos.`);
} catch (gpuErr) {
console.warn("UVCoverageSimulator: WebGPU falhou, usando CPU:", gpuErr.message);
uvResult = this._computeUVonCPU(stations, hourAngles, sinDec, cosDec, lambda);
}
} else {
uvResult = this._computeUVonCPU(stations, hourAngles, sinDec, cosDec, lambda);
}

this.uvData = { ...uvResult, lambda, params };

const accel = (this.gpuAvailable && stations.length >= GPU_MIN_STATIONS_THRESHOLD) ? 'GPU' : 'CPU';
console.log(`UVCoverageSimulator [${accel}]: ${uvResult.nBaselines} baselines, ${uvResult.uPoints.length} pontos UV.`);

this.plotUVCoverage(this.uvData);
this.calculateResolution();
if (!this.uvData._statusSet) {
this.updateStatus(`Cobertura UV gerada (${accel}): ${uvResult.nBaselines} baselines, ${uvResult.uPoints.length} pontos.`);
}
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generateUVCoverage() é async e pode ser acionado múltiplas vezes em sequência (ex.: geração automática de stations). Hoje não há nenhuma proteção contra execuções concorrentes; chamadas mais lentas podem sobrescrever o resultado de chamadas mais novas (race condition) e causar uso desnecessário de CPU/GPU. Sugestão: adicionar um token/counter de geração para descartar resultados antigos ou debouncer específico para UV, e/ou bloquear enquanto uma geração está em andamento.

Copilot uses AI. Check for mistakes.
Comment on lines +302 to +323
// Uniform data: sinDec, cosDec, lambda, nTimesteps, nBaselines
const uniformData = new Float32Array([sinDec, cosDec, lambda, nTimesteps, nBaselines]);

// Create GPU buffers
const baselinesBuffer = device.createBuffer({
size: baselinePairs.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(baselinesBuffer, 0, baselinePairs);

const hourAnglesBuffer = device.createBuffer({
size: hourAngleArray.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(hourAnglesBuffer, 0, hourAngleArray);

const uniformBuffer = device.createBuffer({
size: uniformData.byteLength,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(uniformBuffer, 0, uniformData);

Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O uniformBuffer é criado com size: uniformData.byteLength (20 bytes), mas buffers UNIFORM em WebGPU/WGSL exigem tamanho/alinhamento múltiplo de 16 e o struct Uniforms com 5 campos será padded. Isso deve causar erro de validação ou leitura incorreta, fazendo o caminho GPU falhar. Sugestão: alocar o buffer com tamanho múltiplo de 16 (ex.: 32 bytes) e incluir padding no Float32Array, ou reestruturar os uniforms (ex.: vec4<f32> + campos extras).

Copilot uses AI. Check for mistakes.
lambda: f32,
nTimesteps: f32,
nBaselines: f32,
};
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No WGSL do compute shader, o struct Uniforms está fechado com };. No restante do repo (ex.: shader em js/beam_gpu.js) o padrão é fechar struct apenas com } (sem ;). Esse ; pode fazer a compilação do shader falhar e desativar o caminho WebGPU. Sugestão: remover o ; e manter a sintaxe consistente com os shaders existentes.

Suggested change
};
}

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +129
window.addEventListener('stationsGenerated', () => {
const uvTab = document.querySelector('.tab-button[data-tab="uv-coverage"]') ||
document.querySelector('[data-tab="uv-coverage"]');
const isActive = uvTab && uvTab.classList.contains('active');
if (isActive) {
console.log("UVCoverageSimulator: Evento 'stationsGenerated' recebido. Atualizando cobertura UV.");
this.generateUVCoverage();
}
});
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O listener de stationsGenerated tenta detectar a aba UV ativa usando seletores/IDs que não existem no novo sistema de abas (.tab-button e data-tab="uv-coverage"). Com isso, a atualização automática da cobertura UV nunca dispara quando a aba UV está ativa. Sugestão: checar o painel #tab-uv-coverage (classe .active) ou usar window.tabManager.activeTabId/evento tabChanged para saber quando recalcular.

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +205
_readParams() {
const dec = this.decInput ? parseFloat(this.decInput.value) : this.defaultDec;
const duration = this.durationInput ? parseFloat(this.durationInput.value) : this.defaultDuration;
const timesteps = this.timestepsInput ? parseInt(this.timestepsInput.value, 10) : this.defaultTimesteps;
const freqMHz = this.freqInput ? parseFloat(this.freqInput.value) : this.defaultFreqMHz;
const latitude = this.latitudeInput ? parseFloat(this.latitudeInput.value) : this.defaultLatitude;

return {
dec: isNaN(dec) ? this.defaultDec : dec,
duration: isNaN(duration) ? this.defaultDuration : duration,
timesteps: isNaN(timesteps) || timesteps < 1 ? this.defaultTimesteps : timesteps,
freqHz: isNaN(freqMHz) ? BingoConstants.FREQUENCY_HZ : freqMHz * 1e6,
latitude: isNaN(latitude) ? this.defaultLatitude : latitude
};
}

/**
* Calcula a cobertura UV para todos os pares de estações e passos de tempo.
* Tenta usar WebGPU se disponível para aceleração; caso contrário, usa CPU.
*
* u = Bx*sin(H) + By*cos(H)
* v = -Bx*sin(dec)*cos(H) + By*sin(dec)*sin(H) + Bz*cos(dec)
*
* Bx, By são componentes do baseline no plano local (Bz = 0 para estações coplanares).
* Coordenadas em comprimentos de onda: u_λ = u/λ, v_λ = v/λ.
*/
async generateUVCoverage() {
const stations = this.getStationPositions();
if (!stations || stations.length < 2) {
this.updateStatus("Erro: São necessárias pelo menos 2 estações para calcular a cobertura UV.");
console.error("UVCoverageSimulator: Número insuficiente de estações.");
return;
}

this.updateStatus("Calculando cobertura UV...");

// Aguarda WebGPU ficar pronto (se ainda estiver inicializando)
if (this._gpuReadyPromise) {
await this._gpuReadyPromise;
}

const params = this._readParams();
const decRad = params.dec * BingoConstants.DEG_TO_RAD;
const lambda = BingoConstants.SPEED_OF_LIGHT / params.freqHz;
const sinDec = Math.sin(decRad);
const cosDec = Math.cos(decRad);

// Ângulos horários: centrados em 0, de -duration/2 a +duration/2
const halfDuration = params.duration / 2;
const hourAngles = [];
for (let i = 0; i < params.timesteps; i++) {
const hHours = params.timesteps === 1 ? 0 : -halfDuration + (params.duration * i) / (params.timesteps - 1);
hourAngles.push(hHours * 15 * BingoConstants.DEG_TO_RAD);
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O parâmetro latitude é lido em _readParams() e existe na UI, mas não é usado em nenhum cálculo (nem CPU nem GPU). Isso deixa o controle enganoso para o usuário. Sugestão: ou remover o campo/param, ou incorporar latitude na transformação padrão ENU→UVW (ou explicar explicitamente no UI que a aproximação atual ignora latitude).

Copilot uses AI. Check for mistakes.
Comment on lines +211 to +226
// Redimensiona o canvas do gerador e plots Plotly quando voltar à aba de layout
if (tabId === 'tab-layout') {
setTimeout(() => {
if (window.antennaGenerator?.resizeCanvas) {
window.antennaGenerator.resizeCanvas();
}
// Redimensiona plots Plotly
const plotIds = ['beam-pattern-plot', 'psf-ee-theta-plot'];
plotIds.forEach(id => {
const el = document.getElementById(id);
if (el && typeof Plotly !== 'undefined') {
try { Plotly.Plots.resize(el); } catch(e) { /* plot pode não existir */ }
}
});
}, 100);
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ao voltar para a aba tab-layout, o Leaflet normalmente precisa de invalidateSize() quando o container ficou display:none (abas). Aqui só há resize do canvas e dos plots Plotly; isso pode deixar o mapa em branco ou com tiles desalinhados após trocar de aba. Sugestão: no bloco tabId === 'tab-layout', chamar window.interactiveMap?.map?.invalidateSize(true) (ou expor um método no InteractiveMap).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants