Skip to content

Commit f9b97df

Browse files
authored
Merge pull request #462 from SimmerV/chore
Map RF tools: panel redesign, scan accuracy parity, and scan-from-coverage overlay
2 parents 79a5dd0 + 11852f7 commit f9b97df

3 files changed

Lines changed: 1365 additions & 941 deletions

File tree

frontend/src/pages/Map.tsx

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,9 @@ export function Map() {
331331
// total === 0 means idle / drag preview / terrain fetch
332332
const [coverageProgress, setCoverageProgress] = useState<{ completed: number; total: number }>({ completed: 0, total: 0 });
333333
const [coverageDemSource, setCoverageDemSource] = useState<DemSource | null>(null);
334+
// Keeps the coverage paint up across the activeTool flip when the user
335+
// launches a Scan-from-here overlay from the coverage panel.
336+
const [keepCoveragePaint, setKeepCoveragePaint] = useState(false);
334337
// Drives the land-cover status chip in each panel.
335338
const [coverageClutterStatus, setCoverageClutterStatus] = useState<{ tilesPresent: number; tilesTotal: number } | null>(null);
336339
const [scanClutterStatus, setScanClutterStatus] = useState<{ tilesPresent: number; tilesTotal: number } | null>(null);
@@ -412,6 +415,7 @@ export function Map() {
412415
// Antenna AGL (m); overrides GPS altitude on node-anchored origins
413416
const [coverageAntennaHeightM, setCoverageAntennaHeightM] = useState(2);
414417
const [coverageReliability, setCoverageReliability] = useState<CoverageReliability>("typical");
418+
const [scanReliability, setScanReliability] = useState<CoverageReliability>("typical");
415419
// Ref mirror so the drag-preview closure sees latest without re-binding
416420
const coverageAntennaHeightMRef = useRef(2);
417421
useEffect(() => {
@@ -524,6 +528,9 @@ export function Map() {
524528
const [scanDemSource, setScanDemSource] = useState<DemSource | null>(null);
525529
/** Monotonic request id — stale worker replies are dropped. */
526530
const coverageRequestIdRef = useRef(0);
531+
// Suppresses the redundant coverage recompute the activeTool flip would
532+
// otherwise trigger on Scan-from-here overlay enter/exit.
533+
const skipNextCoverageComputeRef = useRef(false);
527534
/** Lazily-created coverage worker pool; terminated on unmount. */
528535
const coveragePoolRef = useRef<CoverageWorkerPool | null>(null);
529536
const ensureCoveragePool = useCallback((): CoverageWorkerPool => {
@@ -1160,6 +1167,7 @@ export function Map() {
11601167
losHoverMarkerRef.current = null;
11611168
setLosResult(null);
11621169
setCoverageResult(null);
1170+
setKeepCoveragePaint(false);
11631171
setScanSummary(null);
11641172
setIsComputingCoverage(false);
11651173
setIsFetchingCoverageTerrain(false);
@@ -1567,33 +1575,35 @@ export function Map() {
15671575
return;
15681576
}
15691577
const scanBounds = demBoundsAround(origin!, SCAN_RADIUS_KM, 1.05);
1578+
// 2048² rasters match coverage's resolution so per-target ITM sees
1579+
// the same terrain detail the painted prediction does.
15701580
const [{ dem, source: demSourceUsedForScan }, scanClutter, scanCanopy, scanBuildings] = await Promise.all([
15711581
buildDem({
15721582
bounds: scanBounds,
1573-
targetWidth: 1024,
1574-
targetHeight: 1024,
1583+
targetWidth: 2048,
1584+
targetHeight: 2048,
15751585
token: mapboxToken,
15761586
}),
15771587
// Skip individual fetches when their respective models are toggled off.
15781588
scanClutterEnabled
15791589
? buildClutterRaster({
15801590
bounds: scanBounds,
1581-
targetWidth: 1024,
1582-
targetHeight: 1024,
1591+
targetWidth: 2048,
1592+
targetHeight: 2048,
15831593
})
15841594
: Promise.resolve(null),
15851595
scanCanopyEnabled
15861596
? buildCanopyRaster({
15871597
bounds: scanBounds,
1588-
targetWidth: 1024,
1589-
targetHeight: 1024,
1598+
targetWidth: 2048,
1599+
targetHeight: 2048,
15901600
})
15911601
: Promise.resolve(null),
15921602
scanBuildingsEnabled
15931603
? buildBuildingRaster({
15941604
bounds: scanBounds,
1595-
targetWidth: 1024,
1596-
targetHeight: 1024,
1605+
targetWidth: 2048,
1606+
targetHeight: 2048,
15971607
})
15981608
: Promise.resolve(null),
15991609
]);
@@ -1657,6 +1667,11 @@ export function Map() {
16571667
polarization: 1 /* Vertical */,
16581668
groundDielectric: 15,
16591669
groundConductivity: 0.005,
1670+
// Without these, scanAnalysis falls back to 50/50/50 — much
1671+
// more optimistic than coverage's 90/50/70 default.
1672+
timePct: reliabilityPreset(scanReliability).time,
1673+
locationPct: reliabilityPreset(scanReliability).location,
1674+
situationPct: reliabilityPreset(scanReliability).situation,
16601675
}
16611676
: undefined,
16621677
});
@@ -1677,7 +1692,7 @@ export function Map() {
16771692
return () => { cancelled = true; };
16781693
}, [activeTool, toolStep, toolFromId, toolVirtualPos, provider, terrain3D, nodes,
16791694
scanTxDbm, scanAntennaDbi, scanRxAntennaDbi, scanEffectiveSensitivityDbm,
1680-
scanAggressionIdx, scanClutterEnabled, scanCanopyEnabled, scanBuildingsEnabled, scanAntennaHeightM]);
1695+
scanAggressionIdx, scanClutterEnabled, scanCanopyEnabled, scanBuildingsEnabled, scanAntennaHeightM, scanReliability]);
16811696

16821697
// Per-class map visibility filter (compute still runs for hidden classes).
16831698
useEffect(() => {
@@ -1737,17 +1752,25 @@ export function Map() {
17371752
}
17381753
}, [scanHoverId, scanSummary]);
17391754

1740-
// Coverage prediction — runs when Coverage tool reaches result step.
1755+
// Coverage prediction — also runs while a Scan-from-here overlay is active
1756+
// so coverage-setting tweaks through the minimized panel still recompute.
17411757
useEffect(() => {
1742-
if (activeTool !== "coverage" || toolStep !== "result") {
1743-
setCoverageResult(null);
1758+
if ((activeTool !== "coverage" && !keepCoveragePaint) || toolStep !== "result") {
1759+
// Overlay holds onto the result so the paint stays up and the
1760+
// minimized panel keeps showing the reachable summary.
1761+
if (!keepCoveragePaint) setCoverageResult(null);
17441762
setIsComputingCoverage(false);
17451763
setIsFetchingCoverageTerrain(false);
17461764
setCoverageError(null);
17471765
setCoverageProgress({ completed: 0, total: 0 });
17481766
lastRecenteredOriginRef.current = null;
17491767
return;
17501768
}
1769+
// Suppress the redundant recompute on overlay enter/exit (params unchanged).
1770+
if (skipNextCoverageComputeRef.current) {
1771+
skipNextCoverageComputeRef.current = false;
1772+
return;
1773+
}
17511774
if (!terrain3D) {
17521775
setCoverageResult(null);
17531776
setIsComputingCoverage(false);
@@ -2127,13 +2150,14 @@ export function Map() {
21272150
return () => {
21282151
cancelled = true;
21292152
};
2130-
}, [activeTool, toolStep, toolFromId, toolVirtualPos, coverageRadiusKm, coverageAntennaDbi, coverageRxAntennaDbi, coverageRxHeightM, coverageTxDbm, coverageAggressionIdx, coverageClutterEnabled, coverageCanopyEnabled, coverageBuildingsEnabled, coverageMergeOrigins, coverageSensitivityDbm, coverageDetail, coverageAntennaHeightM, coverageReliability, provider, terrain3D, nodes, coverageRetryNonce]);
2153+
}, [activeTool, toolStep, toolFromId, toolVirtualPos, coverageRadiusKm, coverageAntennaDbi, coverageRxAntennaDbi, coverageRxHeightM, coverageTxDbm, coverageAggressionIdx, coverageClutterEnabled, coverageCanopyEnabled, coverageBuildingsEnabled, coverageMergeOrigins, coverageSensitivityDbm, coverageDetail, coverageAntennaHeightM, coverageReliability, provider, terrain3D, nodes, coverageRetryNonce, keepCoveragePaint]);
21312154

2132-
// Hide coverage raster when leaving tool; sources/layers stay for fast re-entry
2155+
// Hide coverage layers when leaving tool; sources/layers stay for fast
2156+
// re-entry. keepCoveragePaint exempts the Scan-from-here overlay.
21332157
useEffect(() => {
21342158
const mb = mbMapRef.current;
21352159
if (!mb) return;
2136-
if (activeTool !== "coverage") {
2160+
if (activeTool !== "coverage" && !keepCoveragePaint) {
21372161
try {
21382162
if (mb.getLayer("coverage-raster")) {
21392163
mb.setLayoutProperty("coverage-raster", "visibility", "none");
@@ -2146,7 +2170,7 @@ export function Map() {
21462170
}
21472171
} catch {}
21482172
}
2149-
}, [activeTool]);
2173+
}, [activeTool, keepCoveragePaint]);
21502174

21512175
useEffect(() => {
21522176
const mb = mbMapRef.current;
@@ -2156,10 +2180,10 @@ export function Map() {
21562180
mb.setLayoutProperty(
21572181
"coverage-contours-line",
21582182
"visibility",
2159-
activeTool === "coverage" && showCoverageContours ? "visible" : "none",
2183+
(activeTool === "coverage" || keepCoveragePaint) && showCoverageContours ? "visible" : "none",
21602184
);
21612185
} catch {}
2162-
}, [activeTool, showCoverageContours, coverageResult]);
2186+
}, [activeTool, showCoverageContours, coverageResult, keepCoveragePaint]);
21632187

21642188
useEffect(() => {
21652189
const mb = mbMapRef.current;
@@ -2169,10 +2193,10 @@ export function Map() {
21692193
mb.setLayoutProperty(
21702194
"coverage-rays-line",
21712195
"visibility",
2172-
activeTool === "coverage" && showCoverageRays ? "visible" : "none",
2196+
(activeTool === "coverage" || keepCoveragePaint) && showCoverageRays ? "visible" : "none",
21732197
);
21742198
} catch {}
2175-
}, [activeTool, showCoverageRays, coverageResult]);
2199+
}, [activeTool, showCoverageRays, coverageResult, keepCoveragePaint]);
21762200

21772201
/** Cluster donut + count text dim. Combines the tool-active dim (when an RF
21782202
* tool is in result step, so the raster reads clearly) with focus-on-hover
@@ -4321,9 +4345,11 @@ export function Map() {
43214345
/>
43224346
)}
43234347

4324-
{/* Floating Coverage panel */}
4325-
{activeTool === "coverage" && toolStep === "result" && (toolFromId || toolVirtualPos) && (
4348+
{/* Stays mounted (force-minimized) during a Scan-from-here overlay so
4349+
the user knows coverage is paused, not closed. */}
4350+
{((activeTool === "coverage") || keepCoveragePaint) && toolStep === "result" && (toolFromId || toolVirtualPos) && (
43264351
<MapCoveragePanel
4352+
overlayMode={keepCoveragePaint}
43274353
result={coverageResult}
43284354
originLabel={
43294355
toolFromId
@@ -4411,6 +4437,27 @@ export function Map() {
44114437
setToolVirtualPos(lngLat);
44124438
mbMapRef.current?.easeTo({ center: lngLat, duration: 600 });
44134439
}}
4440+
onScanFromHere={() => {
4441+
// Mirror coverage's RF settings so the scan results match the
4442+
// painted prediction. Origin (toolFromId / toolVirtualPos) is
4443+
// already shared between the tools.
4444+
setScanHardwareIdx(coverageHardwareIdx);
4445+
setScanAntennaIdx(coverageAntennaIdx);
4446+
setScanAntennaHeightM(coverageAntennaHeightM);
4447+
setScanCustomTxDbm(coverageCustomTxDbm);
4448+
setScanRxHardwareIdx(coverageRxHardwareIdx);
4449+
setScanRxAntennaIdx(coverageRxAntennaIdx);
4450+
setScanPresetIdx(coveragePresetIdx);
4451+
setScanCustomSensDbm(coverageCustomSensDbm);
4452+
setScanAggressionIdx(coverageAggressionIdx);
4453+
setScanClutterEnabled(coverageClutterEnabled);
4454+
setScanCanopyEnabled(coverageCanopyEnabled);
4455+
setScanBuildingsEnabled(coverageBuildingsEnabled);
4456+
setScanReliability(coverageReliability);
4457+
skipNextCoverageComputeRef.current = true;
4458+
setKeepCoveragePaint(true);
4459+
setActiveTool("scan");
4460+
}}
44144461
/>
44154462
)}
44164463

@@ -4444,7 +4491,17 @@ export function Map() {
44444491
demSource={scanDemSource}
44454492
terrainNeeded={!terrain3D}
44464493
onEnableTerrain={() => setTerrain3D(true)}
4447-
onClose={resetTool}
4494+
onClose={() => {
4495+
// Overlay close returns to the coverage view; standalone close
4496+
// does a full reset.
4497+
if (keepCoveragePaint) {
4498+
skipNextCoverageComputeRef.current = true;
4499+
setKeepCoveragePaint(false);
4500+
setActiveTool("coverage");
4501+
} else {
4502+
resetTool();
4503+
}
4504+
}}
44484505
onSelectResult={(id) => {
44494506
// Fly to the target, then open its details panel.
44504507
const n = nodes[id] ?? nodes[`!${id}`];
@@ -4507,6 +4564,8 @@ export function Map() {
45074564
onPresetIdxChange={setScanPresetIdx}
45084565
customSensitivityDbm={scanCustomSensDbm}
45094566
onCustomSensitivityChange={setScanCustomSensDbm}
4567+
reliability={scanReliability}
4568+
onReliabilityChange={setScanReliability}
45104569
/>
45114570
)}
45124571

0 commit comments

Comments
 (0)