diff --git a/server/static/app.js b/server/static/app.js
index c528de7..377b7cd 100644
--- a/server/static/app.js
+++ b/server/static/app.js
@@ -2280,10 +2280,11 @@ function apiDebugReadForm() {
txHeight: apiDebugNumber('apiDebugTxH', 30),
rxHeight: apiDebugNumber('apiDebugRxH', 2),
frequency: apiDebugNumber('apiDebugFreq', 433),
- samples: Math.max(2, Math.min(2048, Math.round(apiDebugNumber('apiDebugSamples', 128)))),
- model: document.getElementById('apiDebugModel')?.value || 'manual',
- coverageModel: document.getElementById('apiDebugCoverageModel')?.value || 'itm',
- includeCanopy: !!document.getElementById('apiDebugIncludeCanopy')?.checked
+ samples: Math.max(2, Math.min(2048, Math.round(apiDebugNumber('apiDebugSamples', 128)))),
+ model: document.getElementById('apiDebugModel')?.value || 'manual',
+ coverageModel: document.getElementById('apiDebugCoverageModel')?.value || 'itm',
+ coverageFormat: document.getElementById('apiDebugCoverageFormat')?.value || 'geojson',
+ includeCanopy: !!document.getElementById('apiDebugIncludeCanopy')?.checked
};
}
@@ -2431,6 +2432,25 @@ function renderApiDebugCoverageLayer(geojson, model) {
return { rendered, levels };
}
+function formatApiDebugCoverageRasterSummary(job, data) {
+ const meta = data?.metadata || {};
+ const jobId = job?.jobId || '—';
+ const format = data?.format || job?.result?.format || meta.format || '—';
+ const artifactPath = `/api/v1/jobs/${encodeURIComponent(jobId)}/artifact`;
+ const artifactUrl = `${apiDebugReadForm().base}${artifactPath}`;
+ const stats = [
+ `resolution ${fmtNum(meta.resolution_m, 0, ' m')}`,
+ `${meta.width ?? '—'}×${meta.height ?? '—'}`,
+ `valid ${meta.valid_pixels ?? '—'} px`,
+ `min ${fmtNum(meta.min_dbm, 1, ' dBm')}`,
+ `max ${fmtNum(meta.max_dbm, 1, ' dBm')}`
+ ].join(' · ');
+ return `Coverage raster: ${escapeHtml(format)} · model ${escapeHtml(job?.result?.model || '—')}
` +
+ `${stats}
` +
+ `Файл: ${escapeHtml(data?.uri || '—')}
` +
+ `Скачать через proxy: artifact`;
+}
+
function renderApiDebugViewshedLayer(geojson) {
return renderApiDebugGeoJsonLayer(geojson, {
style: {
@@ -2980,9 +3000,10 @@ function buildApiDebugRequest(kind) {
azimuth_step_deg: 2,
range_step_m: 50,
include_buildings: true,
+ include_canopy: f.includeCanopy,
include_vegetation: false,
levels_dbm: [-90, -100, -110],
- format: 'geojson'
+ format: f.coverageFormat
}
};
}
@@ -3069,12 +3090,16 @@ async function runApiDebugDemo(kind) {
return;
}
if (kind === 'coverage') {
- const geojson = job.result?.data;
+ const coverageData = job.result?.data;
const model = job.result?.model || apiDebugReadForm().coverageModel;
- const { rendered, levels } = renderApiDebugCoverageLayer(geojson, model);
- const features = geojson?.features?.length ?? 0;
+ if (coverageData?.kind === 'coverage_raster') {
+ apiDebugSetSummary(formatApiDebugCoverageRasterSummary(job, coverageData));
+ return;
+ }
+ const { rendered, levels } = renderApiDebugCoverageLayer(coverageData, model);
+ const features = coverageData?.features?.length ?? 0;
const fsplHint = model === 'fspl'
- ? '
FSPL + omni = идеальный круг (рельеф не учитывается). Для формы по местности — ITM.'
+ ? '
FSPL + omni без surface obstruction даёт круг; сейчас API добавляет buildings/canopy loss, если включены соответствующие флаги.'
: '';
if (rendered) {
apiDebugSetSummary(
@@ -3287,8 +3312,9 @@ function formatApiDebugBudgetSummary(budget) {
return `Link budget (${escapeHtml(model)}): энергетически проходит ${fmtBool(budget.link_viable)} · margin ${fmtNum(budget.fade_margin_db, 1, ' dB')}
` +
`Зона Френеля: ${escapeHtml(quality)} · clear ${fmtBool(budget.fresnel_clear)}
` +
`RX power: ${fmtNum(budget.rx_power_dbm, 1, ' dBm')} · total loss ${fmtNum(budget.total_loss_db, 1, ' dB')}
` +
- `FSPL: ${fmtNum(budget.fspl_db, 1, ' dB')} · diffraction ${fmtNum(budget.diffraction_db, 1, ' dB')} · ` +
- `vegetation ${fmtNum(budget.vegetation_db, 1, ' dB')} · atmospheric ${fmtNum(budget.atmospheric_db, 1, ' dB')}` +
+ `Loss: FSPL ${fmtNum(budget.fspl_db, 1, ' dB')} · active excess ${fmtNum(budget.propagation_excess_db ?? budget.diffraction_db, 1, ' dB')} · ` +
+ `knife-edge ${fmtNum(budget.knife_edge_diffraction_db, 1, ' dB')}
` +
+ `Environment: vegetation ${fmtNum(budget.vegetation_db, 1, ' dB')} · atmospheric ${fmtNum(budget.atmospheric_db, 1, ' dB')}` +
formatDataSourcesLine(budget.data_sources);
}
diff --git a/server/static/index.html b/server/static/index.html
index dfc07e8..d16f6fa 100644
--- a/server/static/index.html
+++ b/server/static/index.html
@@ -50,7 +50,7 @@
-
+
/api/v1/jobs/{id} и подгружает GeoJSON артефакт. Coverage с FSPL даёт круг (без рельефа) — для реалистичного контура выберите ITM./api/v1/jobs/{id}. Coverage умеет GeoJSON/PNG/GeoTIFF; canopy включайте только когда данные уже загружены.