@mytec: iter3.4.0 ready for testing
This commit is contained in:
@@ -65,6 +65,7 @@ export default function App() {
|
||||
const heatmapVisible = useCoverageStore((s) => s.heatmapVisible);
|
||||
const coverageError = useCoverageStore((s) => s.error);
|
||||
const coverageProgress = useCoverageStore((s) => s.progress);
|
||||
const partialPoints = useCoverageStore((s) => s.partialPoints);
|
||||
const calculateCoverageApi = useCoverageStore((s) => s.calculateCoverage);
|
||||
const cancelCalculation = useCoverageStore((s) => s.cancelCalculation);
|
||||
|
||||
@@ -658,20 +659,23 @@ export default function App() {
|
||||
{/* Map */}
|
||||
<div className="flex-1 relative">
|
||||
<MapView onMapClick={handleMapClick} onEditSite={handleEditSite}>
|
||||
{coverageResult && (
|
||||
{/* Show partial results during tiled calculation, or final result */}
|
||||
{(coverageResult || (isCalculating && partialPoints.length > 0)) && (
|
||||
<>
|
||||
<GeographicHeatmap
|
||||
points={coverageResult.points}
|
||||
points={isCalculating && partialPoints.length > 0 ? partialPoints : (coverageResult?.points ?? [])}
|
||||
visible={heatmapVisible}
|
||||
opacity={settings.heatmapOpacity}
|
||||
radiusMeters={settings.heatmapRadius}
|
||||
rsrpThreshold={settings.rsrpThreshold}
|
||||
/>
|
||||
<CoverageBoundary
|
||||
points={coverageResult.points.filter(p => p.rsrp >= settings.rsrpThreshold)}
|
||||
visible={heatmapVisible}
|
||||
resolution={settings.resolution}
|
||||
/>
|
||||
{coverageResult && (
|
||||
<CoverageBoundary
|
||||
points={coverageResult.points.filter(p => p.rsrp >= settings.rsrpThreshold)}
|
||||
visible={heatmapVisible}
|
||||
resolution={settings.resolution}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</MapView>
|
||||
|
||||
@@ -19,15 +19,24 @@ export interface WSProgress {
|
||||
eta_seconds?: number;
|
||||
}
|
||||
|
||||
export interface WSPartialResults {
|
||||
points: Array<Record<string, unknown>>;
|
||||
tile: number;
|
||||
total_tiles: number;
|
||||
progress: number;
|
||||
}
|
||||
|
||||
type ProgressCallback = (progress: WSProgress) => void;
|
||||
type ResultCallback = (data: CoverageResponse) => void;
|
||||
type ErrorCallback = (error: string) => void;
|
||||
type PartialResultsCallback = (data: WSPartialResults) => void;
|
||||
type ConnectionCallback = (connected: boolean) => void;
|
||||
|
||||
interface PendingCalc {
|
||||
onProgress?: ProgressCallback;
|
||||
onResult: ResultCallback;
|
||||
onError: ErrorCallback;
|
||||
onPartialResults?: PartialResultsCallback;
|
||||
}
|
||||
|
||||
class WebSocketService {
|
||||
@@ -110,6 +119,16 @@ class WebSocketService {
|
||||
console.warn('[WS] progress msg but no pending calc:', calcId, msg.phase, msg.progress);
|
||||
}
|
||||
break;
|
||||
case 'partial_results':
|
||||
if (pending?.onPartialResults) {
|
||||
pending.onPartialResults({
|
||||
points: msg.points,
|
||||
tile: msg.tile,
|
||||
total_tiles: msg.total_tiles,
|
||||
progress: msg.progress,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'result':
|
||||
if (pending) {
|
||||
pending.onResult(msg.data);
|
||||
@@ -159,13 +178,14 @@ class WebSocketService {
|
||||
onResult: ResultCallback,
|
||||
onError: ErrorCallback,
|
||||
onProgress?: ProgressCallback,
|
||||
onPartialResults?: PartialResultsCallback,
|
||||
): string | undefined {
|
||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const calcId = crypto.randomUUID();
|
||||
this._pendingCalcs.set(calcId, { onProgress, onResult, onError });
|
||||
this._pendingCalcs.set(calcId, { onProgress, onResult, onError, onPartialResults });
|
||||
|
||||
this.ws.send(JSON.stringify({
|
||||
type: 'calculate',
|
||||
|
||||
@@ -20,6 +20,11 @@ interface CoverageState {
|
||||
progress: WSProgress | null;
|
||||
activeCalcId: string | null;
|
||||
|
||||
// Progressive rendering — accumulates points as tiles complete
|
||||
partialPoints: CoverageResult['points'];
|
||||
tilesCompleted: number;
|
||||
totalTiles: number;
|
||||
|
||||
setResult: (result: CoverageResult | null) => void;
|
||||
clearCoverage: () => void;
|
||||
setIsCalculating: (val: boolean) => void;
|
||||
@@ -166,6 +171,9 @@ export const useCoverageStore = create<CoverageState>((set, get) => ({
|
||||
error: null,
|
||||
progress: null,
|
||||
activeCalcId: null,
|
||||
partialPoints: [],
|
||||
tilesCompleted: 0,
|
||||
totalTiles: 0,
|
||||
|
||||
setResult: (result) => set({ result }),
|
||||
clearCoverage: () => set({ result: null, error: null }),
|
||||
@@ -195,7 +203,7 @@ export const useCoverageStore = create<CoverageState>((set, get) => ({
|
||||
|
||||
const apiSettings = buildApiSettings(settings);
|
||||
|
||||
set({ isCalculating: true, error: null, progress: null, activeCalcId: null });
|
||||
set({ isCalculating: true, error: null, progress: null, activeCalcId: null, partialPoints: [], tilesCompleted: 0, totalTiles: 0 });
|
||||
|
||||
// Try WebSocket first (provides real-time progress)
|
||||
if (wsService.connected) {
|
||||
@@ -206,7 +214,7 @@ export const useCoverageStore = create<CoverageState>((set, get) => ({
|
||||
(data) => {
|
||||
try {
|
||||
const result = responseToResult(data, settings);
|
||||
set({ result, isCalculating: false, error: null, progress: null, activeCalcId: null });
|
||||
set({ result, isCalculating: false, error: null, progress: null, activeCalcId: null, partialPoints: [], tilesCompleted: 0, totalTiles: 0 });
|
||||
// Show success toast for WS result
|
||||
const addToast = useToastStore.getState().addToast;
|
||||
if (result.points.length === 0) {
|
||||
@@ -227,18 +235,40 @@ export const useCoverageStore = create<CoverageState>((set, get) => ({
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Coverage] Failed to process result:', err);
|
||||
set({ isCalculating: false, error: 'Failed to process coverage result', progress: null, activeCalcId: null });
|
||||
set({ isCalculating: false, error: 'Failed to process coverage result', progress: null, activeCalcId: null, partialPoints: [], tilesCompleted: 0, totalTiles: 0 });
|
||||
}
|
||||
},
|
||||
// onError
|
||||
(error) => {
|
||||
set({ isCalculating: false, error, progress: null, activeCalcId: null });
|
||||
set({ isCalculating: false, error, progress: null, activeCalcId: null, partialPoints: [], tilesCompleted: 0, totalTiles: 0 });
|
||||
useToastStore.getState().addToast(`Calculation failed: ${error}`, 'error');
|
||||
},
|
||||
// onProgress
|
||||
(progress) => {
|
||||
set({ progress });
|
||||
},
|
||||
// onPartialResults — accumulate points as tiles complete
|
||||
(data) => {
|
||||
const newPoints = data.points.map((p: Record<string, unknown>) => ({
|
||||
lat: p.lat as number,
|
||||
lon: p.lon as number,
|
||||
rsrp: p.rsrp as number,
|
||||
distance: p.distance as number,
|
||||
has_los: p.has_los as boolean,
|
||||
terrain_loss: p.terrain_loss as number,
|
||||
building_loss: p.building_loss as number,
|
||||
reflection_gain: (p.reflection_gain as number) ?? 0,
|
||||
vegetation_loss: (p.vegetation_loss as number) ?? 0,
|
||||
rain_loss: (p.rain_loss as number) ?? 0,
|
||||
indoor_loss: (p.indoor_loss as number) ?? 0,
|
||||
atmospheric_loss: (p.atmospheric_loss as number) ?? 0,
|
||||
}));
|
||||
set((state) => ({
|
||||
partialPoints: [...state.partialPoints, ...newPoints],
|
||||
tilesCompleted: data.tile + 1,
|
||||
totalTiles: data.total_tiles,
|
||||
}));
|
||||
},
|
||||
);
|
||||
|
||||
if (calcId) {
|
||||
@@ -278,6 +308,6 @@ export const useCoverageStore = create<CoverageState>((set, get) => ({
|
||||
wsService.cancel(activeCalcId);
|
||||
}
|
||||
api.cancelCalculation();
|
||||
set({ isCalculating: false, progress: null, activeCalcId: null });
|
||||
set({ isCalculating: false, progress: null, activeCalcId: null, partialPoints: [], tilesCompleted: 0, totalTiles: 0 });
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user