updated api

This commit is contained in:
2026-06-24 10:17:50 +03:00
parent fd61ca0726
commit cd82244dcd
2 changed files with 122 additions and 0 deletions
+120
View File
@@ -1873,6 +1873,91 @@ function renderApiDebugViewshedLayer(geojson) {
}); });
} }
function fitApiDebugLayer(layer) {
const bounds = layer?.getBounds?.();
if (bounds?.isValid?.()) {
setMapViewProgrammatically(() => map.fitBounds(bounds, { padding: [40, 40], maxZoom: 16 }));
}
}
function renderApiDebugFresnelSlice(data) {
clearApiDebugCoverage();
clearApiDebugBeam();
const layers = [];
const pathLayer = data?.path_geojson ? L.geoJSON(data.path_geojson, {
style: { color: '#4fc3f7', weight: 3, opacity: 0.95 }
}) : null;
const fresnelLayer = data?.fresnel_geojson ? L.geoJSON(data.fresnel_geojson, {
style: {
color: '#ffd54f',
weight: 2,
opacity: 0.95,
fillColor: '#ffd54f',
fillOpacity: 0.2
}
}) : null;
const buildingsLayer = data?.buildings_geojson ? L.geoJSON(data.buildings_geojson, {
style: {
color: '#ff1744',
weight: 1.5,
opacity: 0.95,
fillColor: '#ff1744',
fillOpacity: 0.3
},
onEachFeature: (feature, layer) => {
const p = feature.properties || {};
const parts = [];
if (p.height_m != null) parts.push(`height ${fmtNum(p.height_m, 1, ' m')}`);
if (p.intersection_area_m2 != null) parts.push(`пересечение ${fmtNum(p.intersection_area_m2, 1, ' m²')}`);
if (p.building_type) parts.push(escapeHtml(p.building_type));
if (parts.length) layer.bindPopup(parts.join(' · '));
}
}) : null;
[fresnelLayer, buildingsLayer, pathLayer].forEach(layer => {
if (layer) layers.push(layer);
});
if (!layers.length) {
apiDebugSetSummary('Ответ fresnel-slice не содержит GeoJSON слоёв.');
return;
}
apiDebugCoverageLayer = L.featureGroup(layers).addTo(map);
fitApiDebugLayer(apiDebugCoverageLayer);
const buildings = data?.buildings_geojson?.features?.length ?? 0;
const profilePoints = data?.profile?.length ?? 0;
apiDebugSetSummary(
`<b>Срез зоны Френеля:</b> ${profilePoints} точек профиля · ${buildings} зданий пересекают область<br>` +
`Жёлтая область — горизонтальный срез 60% зоны Френеля, красные полигоны — пересекающие здания.` +
formatDataSourcesLine(data?.data_sources)
);
}
function renderApiDebugBuildings(data) {
const features = data?.features?.length ?? 0;
const rendered = renderApiDebugGeoJsonLayer(data, {
style: {
color: '#ff1744',
weight: 1.3,
opacity: 0.95,
fillColor: '#ff1744',
fillOpacity: 0.22
},
onEachFeature: (feature, layer) => {
const p = feature.properties || {};
const parts = [];
if (p.height_m != null) parts.push(`height ${fmtNum(p.height_m, 1, ' m')}`);
if (p.levels != null) parts.push(`${p.levels} эт.`);
if (p.building_type) parts.push(escapeHtml(p.building_type));
if (p.source) parts.push(escapeHtml(p.source));
if (parts.length) layer.bindPopup(parts.join(' · '));
}
});
apiDebugSetSummary(
rendered
? `<b>Здания вдоль линии:</b> ${features} объектов в буфере 50 m${formatDataSourcesLine(data?.data_sources)}`
: 'Ответ buildings/query не содержит GeoJSON features.'
);
}
async function fetchApiDebugJobArtifact(jobId) { async function fetchApiDebugJobArtifact(jobId) {
const f = apiDebugReadForm(); const f = apiDebugReadForm();
const url = `${f.base}/api/v1/jobs/${encodeURIComponent(jobId)}/artifact`; const url = `${f.base}/api/v1/jobs/${encodeURIComponent(jobId)}/artifact`;
@@ -2221,6 +2306,23 @@ function buildApiDebugRequest(kind) {
} }
}; };
} }
if (kind === 'fresnelSlice') {
return {
method: 'POST',
path: '/api/v1/terrain/fresnel-slice',
body: {
tx,
rx,
frequency_mhz: f.frequency,
fresnel_fraction: 0.6,
include_buildings: true,
include_canopy: f.includeCanopy,
k_factor: 1.333,
samples: f.samples,
building_limit: 5000
}
};
}
if (kind === 'budget') { if (kind === 'budget') {
return { return {
method: 'POST', method: 'POST',
@@ -2237,6 +2339,22 @@ function buildApiDebugRequest(kind) {
} }
}; };
} }
if (kind === 'buildings') {
return {
method: 'POST',
path: '/api/v1/buildings/query',
body: {
path: {
type: 'LineString',
coordinates: [
[f.tx.lon, f.tx.lat],
[f.rx.lon, f.rx.lat]
]
},
buffer_m: 50
}
};
}
if (kind === 'landcover') { if (kind === 'landcover') {
return { return {
method: 'POST', method: 'POST',
@@ -2429,7 +2547,9 @@ async function runApiDebugDemo(kind) {
} }
if (kind === 'profile') renderApiDebugProfile(data, null, null); if (kind === 'profile') renderApiDebugProfile(data, null, null);
else if (kind === 'los') renderApiDebugProfile(null, data, null); else if (kind === 'los') renderApiDebugProfile(null, data, null);
else if (kind === 'fresnelSlice') renderApiDebugFresnelSlice(data);
else if (kind === 'budget') apiDebugSetSummary(formatApiDebugBudgetSummary(data)); else if (kind === 'budget') apiDebugSetSummary(formatApiDebugBudgetSummary(data));
else if (kind === 'buildings') renderApiDebugBuildings(data);
else if (kind === 'landcover') apiDebugSetSummary(formatApiDebugLandcoverSummary(data)); else if (kind === 'landcover') apiDebugSetSummary(formatApiDebugLandcoverSummary(data));
else if (kind === 'elevation') apiDebugSetSummary(formatApiDebugPointSummary(data)); else if (kind === 'elevation') apiDebugSetSummary(formatApiDebugPointSummary(data));
else if (kind === 'beam') renderApiDebugBeam(data); else if (kind === 'beam') renderApiDebugBeam(data);
+2
View File
@@ -71,7 +71,9 @@
<button type="button" data-api-demo="elevation">Высота TX</button> <button type="button" data-api-demo="elevation">Высота TX</button>
<button type="button" data-api-demo="profile">Только профиль</button> <button type="button" data-api-demo="profile">Только профиль</button>
<button type="button" data-api-demo="los">Только LOS/Френель</button> <button type="button" data-api-demo="los">Только LOS/Френель</button>
<button type="button" data-api-demo="fresnelSlice">Срез Френеля</button>
<button type="button" data-api-demo="budget">Только link budget</button> <button type="button" data-api-demo="budget">Только link budget</button>
<button type="button" data-api-demo="buildings">Здания вдоль линии</button>
<button type="button" data-api-demo="landcover">Landcover path</button> <button type="button" data-api-demo="landcover">Landcover path</button>
<button type="button" data-api-demo="beam">Сектор антенны</button> <button type="button" data-api-demo="beam">Сектор антенны</button>
</div> </div>