@mytec: iter3.7.0 start, gpu calc int

This commit is contained in:
2026-02-03 22:41:08 +02:00
parent a61753c642
commit 6cd9d869cc
29 changed files with 2288 additions and 28 deletions

View File

@@ -444,11 +444,14 @@ export default function App() {
);
} else {
const timeStr = result.calculationTime.toFixed(1);
const firstSite = sites.find((s) => s.visible);
const freqStr = firstSite ? ` \u2022 ${firstSite.frequency} MHz` : '';
const presetStr = settings.preset ? ` \u2022 ${settings.preset}` : '';
const modelsStr = result.modelsUsed?.length
? ` ${result.modelsUsed.length} models`
? ` \u2022 ${result.modelsUsed.length} models`
: '';
addToast(
`Calculated ${result.totalPoints.toLocaleString()} points in ${timeStr}s${modelsStr}`,
`${result.totalPoints.toLocaleString()} pts \u2022 ${timeStr}s${presetStr}${freqStr}${modelsStr}`,
'success'
);
}
@@ -481,7 +484,7 @@ export default function App() {
return (
<div className="h-screen w-screen flex flex-col bg-gray-100 dark:bg-dark-bg">
{/* Header */}
<header className="bg-slate-800 dark:bg-slate-900 text-white px-4 py-2 flex items-center justify-between flex-shrink-0 z-10">
<header className="bg-slate-800 dark:bg-slate-900 text-white px-4 py-2 flex items-center justify-between flex-shrink-0 z-[1010]">
<div className="flex items-center gap-2">
<span className="text-base font-bold">RFCP</span>
<span className="text-xs text-slate-400 hidden sm:inline">
@@ -684,6 +687,7 @@ export default function App() {
points={coverageResult.points.filter(p => p.rsrp >= settings.rsrpThreshold)}
visible={showBoundary}
resolution={settings.resolution}
boundary={coverageResult.boundary}
/>
)}
</>

View File

@@ -1,8 +1,8 @@
/**
* Renders a dashed polyline around the coverage zone boundary.
*
* Uses @turf/concave to compute a concave hull (alpha shape) per site,
* which correctly follows sector/wedge shapes — not just convex circles.
* Prefers server-computed boundary if available (shapely concave_hull).
* Falls back to client-side @turf/concave computation.
*
* Performance: ~20-50ms for 10k points (runs once per coverage change).
*/
@@ -12,7 +12,7 @@ import { useMap } from 'react-leaflet';
import L from 'leaflet';
import concave from '@turf/concave';
import { featureCollection, point } from '@turf/helpers';
import type { CoveragePoint } from '@/types/index.ts';
import type { CoveragePoint, BoundaryPoint } from '@/types/index.ts';
import { logger } from '@/utils/logger.ts';
interface CoverageBoundaryProps {
@@ -21,6 +21,7 @@ interface CoverageBoundaryProps {
resolution: number; // meters — controls concave hull detail
color?: string;
weight?: number;
boundary?: BoundaryPoint[]; // server-provided boundary (preferred)
}
export default function CoverageBoundary({
@@ -29,13 +30,25 @@ export default function CoverageBoundary({
resolution,
color = '#ffffff', // white — visible against red-to-blue gradient
weight = 2,
boundary,
}: CoverageBoundaryProps) {
const map = useMap();
const layerRef = useRef<L.LayerGroup | null>(null);
// Compute boundary paths grouped by site
// Compute boundary paths - prefer server boundary, fallback to client-side
const boundaryPaths = useMemo(() => {
if (!visible || points.length === 0) return [];
if (!visible) return [];
// Use server-provided boundary if available
if (boundary && boundary.length >= 3) {
const serverPath: L.LatLngExpression[] = boundary.map(
(p) => [p.lat, p.lon] as L.LatLngExpression
);
return [serverPath];
}
// Fallback to client-side computation
if (points.length === 0) return [];
// Group points by siteId (fallback to 'all' when siteId not available from API)
const bySite = new Map<string, CoveragePoint[]>();
@@ -61,7 +74,7 @@ export default function CoverageBoundary({
}
return paths;
}, [points, visible, resolution]);
}, [points, visible, resolution, boundary]);
// Render / cleanup polylines
useEffect(() => {

View File

@@ -121,6 +121,7 @@ export default function MeasurementTool({ enabled, onComplete, onProfileRequest
<button
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
onProfileRequest(points[0], points[points.length - 1]);
}}
style={{

View File

@@ -33,6 +33,13 @@ export default function GPUIndicator() {
return () => document.removeEventListener('mousedown', handler);
}, [open]);
// Auto-fetch diagnostics when dropdown opens and only CPU available
useEffect(() => {
if (open && status?.active_backend === 'cpu' && !diagnostics) {
api.getGPUDiagnostics().then(setDiagnostics).catch(() => {});
}
}, [open, status?.active_backend, diagnostics]);
if (!status) return null;
const isGPU = status.active_backend !== 'cpu';
@@ -119,15 +126,30 @@ export default function GPUIndicator() {
<div className="text-[10px] text-yellow-600 dark:text-yellow-400 mb-2">
No GPU detected. For faster calculations:
</div>
<div className="text-[10px] text-gray-500 dark:text-dark-muted space-y-0.5">
<div>NVIDIA: <code className="bg-gray-100 dark:bg-dark-border px-1 rounded">pip install cupy-cuda12x</code></div>
<div>Intel/AMD: <code className="bg-gray-100 dark:bg-dark-border px-1 rounded">pip install pyopencl</code></div>
</div>
{diagnostics?.is_wsl ? (
<div className="text-[10px] text-gray-500 dark:text-dark-muted space-y-1">
<div className="text-[9px] text-gray-400 dark:text-dark-muted mb-1">WSL2 detected - use pip3:</div>
<div className="bg-gray-100 dark:bg-dark-border px-2 py-1 rounded font-mono text-[9px] break-all">
pip3 install cupy-cuda12x --break-system-packages
</div>
<div className="text-[9px] text-gray-400 dark:text-dark-muted mt-1">Then restart RFCP</div>
</div>
) : (
<div className="text-[10px] text-gray-500 dark:text-dark-muted space-y-0.5">
<div>NVIDIA: <code className="bg-gray-100 dark:bg-dark-border px-1 rounded">pip install cupy-cuda12x</code></div>
<div>Intel/AMD: <code className="bg-gray-100 dark:bg-dark-border px-1 rounded">pip install pyopencl</code></div>
</div>
)}
{typeof diagnostics?.nvidia_smi === 'string' && diagnostics.nvidia_smi !== 'not found or error' && (
<div className="mt-2 text-[9px] text-green-600 dark:text-green-400">
GPU hardware found: {diagnostics.nvidia_smi.split(',')[0]}
</div>
)}
<button
onClick={handleRunDiagnostics}
className="mt-2 w-full text-[10px] text-blue-600 dark:text-blue-400 hover:underline text-left"
>
Run Diagnostics
{diagnostics ? 'Refresh Diagnostics' : 'Run Diagnostics'}
</button>
</div>
)}

View File

@@ -75,6 +75,11 @@ export interface ApiCoverageStats {
points_with_atmospheric_loss: number;
}
export interface ApiBoundaryPoint {
lat: number;
lon: number;
}
export interface CoverageResponse {
points: ApiCoveragePoint[];
count: number;
@@ -82,6 +87,7 @@ export interface CoverageResponse {
stats: ApiCoverageStats;
computation_time: number;
models_used: string[];
boundary?: ApiBoundaryPoint[];
}
export interface Preset {

View File

@@ -98,6 +98,7 @@ function responseToResult(response: CoverageResponse, settings: CoverageSettings
settings: settings,
stats: response.stats as CoverageApiStats,
modelsUsed: response.models_used,
boundary: response.boundary,
};
}
@@ -251,11 +252,14 @@ export const useCoverageStore = create<CoverageState>((set, get) => ({
addToast('No coverage points. Try increasing radius.', 'warning');
} else {
const timeStr = result.calculationTime.toFixed(1);
const firstSite = useSitesStore.getState().sites.find((s) => s.visible);
const freqStr = firstSite ? ` \u2022 ${firstSite.frequency} MHz` : '';
const presetStr = settings.preset ? ` \u2022 ${settings.preset}` : '';
const modelsStr = result.modelsUsed?.length
? ` \u2022 ${result.modelsUsed.length} models`
: '';
addToast(
`Calculated ${result.totalPoints.toLocaleString()} points in ${timeStr}s${modelsStr}`,
`${result.totalPoints.toLocaleString()} pts \u2022 ${timeStr}s${presetStr}${freqStr}${modelsStr}`,
'success'
);
}

View File

@@ -15,6 +15,11 @@ export interface CoveragePoint {
atmospheric_loss?: number; // dB atmospheric absorption
}
export interface BoundaryPoint {
lat: number;
lon: number;
}
export interface CoverageResult {
points: CoveragePoint[];
calculationTime: number; // seconds (was ms for browser calc)
@@ -23,6 +28,7 @@ export interface CoverageResult {
// API-provided fields
stats?: CoverageApiStats;
modelsUsed?: string[];
boundary?: BoundaryPoint[]; // server-computed coverage boundary
}
export interface CoverageApiStats {

View File

@@ -5,5 +5,6 @@ export type {
CoverageSettings,
CoverageApiStats,
GridPoint,
BoundaryPoint,
} from './coverage.ts';
export type { FrequencyBand } from './frequency.ts';