Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d8c406d
Initial plan
Copilot Feb 10, 2026
5fb3f1e
Add tab navigation manager (js/tabs.js)
Copilot Feb 10, 2026
91461de
Add Station Manager module (js/stations.js)
Copilot Feb 10, 2026
00e0159
Add OSKAR INI file generator module (js/ini_generator.js)
Copilot Feb 10, 2026
c24bac9
Add Sky Model Generator module (js/sky_model.js)
Copilot Feb 10, 2026
14bc237
Add UV Coverage Visualization module (js/uv_coverage.js)
Copilot Feb 10, 2026
87fff1a
Add tab system, station manager, OSKAR INI generator, sky model gener…
Copilot Feb 10, 2026
a4544cb
Fix station spacing slider range, fix tab ARIA attributes
Copilot Feb 10, 2026
6414dca
Add ECEF/ENU coordinate export formats with WGS84→ECEF→ENU conversion
Copilot Feb 10, 2026
6ef49fb
Address code review: fix UV single timestep, improve CSS contrast, do…
Copilot Feb 10, 2026
828d80d
Implement UV plot fix, station-map layout, station-to-map integration…
Copilot Feb 10, 2026
8e74d0f
Address code review: fix edge cases, add code comments, normalize CSS…
Copilot Feb 10, 2026
47e503a
Add map distance toggle, INI [General] section, sky-INI integration, …
Copilot Feb 10, 2026
0f16dc2
Fix syntax error in downloadIni method and add JSZip warning
Copilot Feb 10, 2026
199abd7
Fix UV chart vertical positioning, file selector popup, CRLF line end…
Copilot Feb 11, 2026
12d104b
Address code review: extract shared getOsLineEnding utility, use user…
Copilot Feb 11, 2026
a7ad3e4
Fix imager preview height, file path selector (prompt), imager-interf…
Copilot Feb 11, 2026
8662043
Address code review: rename variable and use descriptive ID prefix fo…
Copilot Feb 11, 2026
690b5ed
Enhance station layouts, fix file path UX, center sky table, remove U…
Copilot Feb 11, 2026
67e0e2d
Address code review: add GPU_MIN_STATIONS constant, await GPU init, m…
Copilot Feb 11, 2026
2a55856
Add real-time station generation on parameter change and coordinate f…
Copilot Feb 11, 2026
505c499
Address code review: reuse existing defs variable in _updateExtraPara…
Copilot Feb 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
658 changes: 647 additions & 11 deletions css/styles.css

Large diffs are not rendered by default.

359 changes: 340 additions & 19 deletions index.html

Large diffs are not rendered by default.

121 changes: 120 additions & 1 deletion js/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ class OskarLayoutExporter {
'export-layout-wgs84',
'export-position',
'export-station-layout',
'export-tile-layout'
'export-tile-layout',
'export-layout-ecef',
'export-layout-enu'
];
textareaIds.forEach(id => this._addSingleCopyButtonToTextarea(id));
}
Expand Down Expand Up @@ -217,6 +219,8 @@ class OskarLayoutExporter {
// this.updateBingoPositionField(); // Já chamado no construtor, é fixo.
this.updateStationLayoutField();
// this.updateTileLayoutField(); // Já chamado no construtor e generateSingleTileLayout, é fixo.
this.updateLayoutEcefField();
this.updateLayoutEnuField();

console.log("Campos de exportação OSKAR atualizados com os dados mais recentes.");
}
Expand Down Expand Up @@ -296,6 +300,102 @@ class OskarLayoutExporter {
return '';
}).filter(line => line).join('\n');
}

// --- Conversões de Coordenadas (WGS84 → ECEF → ENU) ---

/**
* Converte coordenadas WGS84 (lat, lon, alt) para ECEF (X, Y, Z).
* @param {number} latDeg Latitude em graus
* @param {number} lonDeg Longitude em graus
* @param {number} altM Altitude em metros
* @returns {{x: number, y: number, z: number}} Coordenadas ECEF em metros
*/
wgs84ToEcef(latDeg, lonDeg, altM) {
const a = 6378137.0; // WGS84 semi-eixo maior
const f = 1.0 / 298.257223563; // WGS84 achatamento
const e2 = 2 * f - f * f; // excentricidade ao quadrado

const latRad = latDeg * Math.PI / 180;
const lonRad = lonDeg * Math.PI / 180;
const sinLat = Math.sin(latRad);
const cosLat = Math.cos(latRad);
const sinLon = Math.sin(lonRad);
const cosLon = Math.cos(lonRad);

const N = a / Math.sqrt(1 - e2 * sinLat * sinLat);

return {
x: (N + altM) * cosLat * cosLon,
y: (N + altM) * cosLat * sinLon,
z: (N * (1 - e2) + altM) * sinLat
};
}

/**
* Converte coordenadas ECEF para ENU relativas a um ponto de referência.
* @param {number} x ECEF X
* @param {number} y ECEF Y
* @param {number} z ECEF Z
* @param {number} refLatDeg Latitude de referência em graus
* @param {number} refLonDeg Longitude de referência em graus
* @param {number} refAltM Altitude de referência em metros
* @returns {{e: number, n: number, u: number}} Coordenadas ENU em metros
*/
ecefToEnu(x, y, z, refLatDeg, refLonDeg, refAltM) {
const ref = this.wgs84ToEcef(refLatDeg, refLonDeg, refAltM);
const dx = x - ref.x;
const dy = y - ref.y;
const dz = z - ref.z;

const latRad = refLatDeg * Math.PI / 180;
const lonRad = refLonDeg * Math.PI / 180;
const sinLat = Math.sin(latRad);
const cosLat = Math.cos(latRad);
const sinLon = Math.sin(lonRad);
const cosLon = Math.cos(lonRad);

return {
e: -sinLon * dx + cosLon * dy,
n: -sinLat * cosLon * dx - sinLat * sinLon * dy + cosLat * dz,
u: cosLat * cosLon * dx + cosLat * sinLon * dy + sinLat * dz
};
}

/**
* Atualiza o textarea para layout_ecef.txt (Coordenadas ECEF das estações).
*/
updateLayoutEcefField() {
const textarea = document.getElementById('export-layout-ecef');
if (!textarea) return;

if (!this.selectedStationsCoords || this.selectedStationsCoords.length === 0) {
textarea.value = 'Nenhuma estação selecionada.\nGere stations primeiro.';
return;
}
textarea.value = this.selectedStationsCoords.map(station => {
const ecef = this.wgs84ToEcef(station.lat || 0, station.lon || 0, station.alt || 0);
return `${ecef.x.toFixed(4)},${ecef.y.toFixed(4)},${ecef.z.toFixed(4)}`;
}).join('\n');
}

/**
* Atualiza o textarea para layout_enu.txt (Coordenadas ENU relativas ao BINGO Central).
*/
updateLayoutEnuField() {
const textarea = document.getElementById('export-layout-enu');
if (!textarea) return;

if (!this.selectedStationsCoords || this.selectedStationsCoords.length === 0) {
textarea.value = 'Nenhuma estação selecionada.\nGere stations primeiro.';
return;
}
textarea.value = this.selectedStationsCoords.map(station => {
const ecef = this.wgs84ToEcef(station.lat || 0, station.lon || 0, station.alt || 0);
const enu = this.ecefToEnu(ecef.x, ecef.y, ecef.z,
BINGO_CENTRAL_LATITUDE, BINGO_CENTRAL_LONGITUDE, BINGO_CENTRAL_ALTITUDE);
return `${enu.e.toFixed(4)},${enu.n.toFixed(4)},${enu.u.toFixed(4)}`;
}).join('\n');
}
} // Fim da classe OskarLayoutExporter

// --- Inicialização da Exportação e Funcionalidade de Download ZIP ---
Expand Down Expand Up @@ -327,6 +427,17 @@ document.addEventListener('DOMContentLoaded', () => {
}
};

// --- Seletor de formato de coordenadas (ECEF / ENU) ---
const coordRadios = document.querySelectorAll('input[name="coord-format"]');
const ecefPanel = document.getElementById('coord-format-ecef');
const enuPanel = document.getElementById('coord-format-enu');
coordRadios.forEach(radio => {
radio.addEventListener('change', () => {
if (ecefPanel) ecefPanel.style.display = radio.value === 'ecef' ? '' : 'none';
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.
// --- Lógica para o Botão de Download ZIP ---
const downloadBtn = document.getElementById('download-zip-btn');
const filenameInput = document.getElementById('zip-filename-input');
Expand All @@ -350,10 +461,18 @@ document.addEventListener('DOMContentLoaded', () => {
const contentPosition = document.getElementById('export-position').value;
const contentStationLayout = document.getElementById('export-station-layout').value;
const contentTileLayout = document.getElementById('export-tile-layout').value;
const contentEcef = document.getElementById('export-layout-ecef')?.value || '';
const contentEnu = document.getElementById('export-layout-enu')?.value || '';

// Adiciona os arquivos ao ZIP com a estrutura de diretórios OSKAR.
zip.file("layout_wgs84.txt", contentWGS84);
zip.file("position.txt", contentPosition);
if (contentEcef && !contentEcef.startsWith('Nenhuma')) {
zip.file("layout_ecef.txt", contentEcef);
}
if (contentEnu && !contentEnu.startsWith('Nenhuma')) {
zip.file("layout_enu.txt", contentEnu);
}
const stationFolder = zip.folder("station"); // Cria pasta "station"
stationFolder.file("layout.txt", contentStationLayout);
const tileFolder = stationFolder.folder("tile"); // Cria pasta "station/tile"
Expand Down
Loading
Loading