@@ -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