@mytec: iter3.7.0 start, gpu calc int
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -5,5 +5,6 @@ export type {
|
||||
CoverageSettings,
|
||||
CoverageApiStats,
|
||||
GridPoint,
|
||||
BoundaryPoint,
|
||||
} from './coverage.ts';
|
||||
export type { FrequencyBand } from './frequency.ts';
|
||||
|
||||
Reference in New Issue
Block a user