/** * Backend API client for RFCP coverage calculation */ import { getApiBaseUrl } from '@/lib/desktop.ts'; const API_BASE = getApiBaseUrl(); // === Request types === export interface ApiSiteParams { lat: number; lon: number; height: number; power: number; // dBm gain: number; // dBi frequency: number; // MHz azimuth?: number; beamwidth?: number; } export interface ApiCoverageSettings { radius: number; // meters resolution: number; // meters min_signal: number; // dBm preset?: 'fast' | 'standard' | 'detailed' | 'full'; use_terrain?: boolean; use_buildings?: boolean; use_materials?: boolean; use_dominant_path?: boolean; use_street_canyon?: boolean; use_reflections?: boolean; use_water_reflection?: boolean; use_vegetation?: boolean; season?: 'summer' | 'winter' | 'spring' | 'autumn'; rain_rate?: number; indoor_loss_type?: string; use_atmospheric?: boolean; temperature_c?: number; humidity_percent?: number; } export interface CoverageRequest { sites: ApiSiteParams[]; settings: ApiCoverageSettings; } // === Response types === export interface ApiCoveragePoint { lat: number; lon: number; rsrp: number; distance: number; has_los: boolean; terrain_loss: number; building_loss: number; reflection_gain: number; vegetation_loss: number; rain_loss: number; indoor_loss: number; atmospheric_loss: number; } export interface ApiCoverageStats { min_rsrp: number; max_rsrp: number; avg_rsrp: number; los_percentage: number; points_with_buildings: number; points_with_terrain_loss: number; points_with_reflection_gain: number; points_with_vegetation_loss: number; points_with_rain_loss: number; points_with_indoor_loss: number; points_with_atmospheric_loss: number; } export interface CoverageResponse { points: ApiCoveragePoint[]; count: number; settings: ApiCoverageSettings; stats: ApiCoverageStats; computation_time: number; models_used: string[]; } export interface Preset { description: string; use_terrain: boolean; use_buildings: boolean; use_materials: boolean; use_dominant_path: boolean; use_street_canyon: boolean; use_reflections: boolean; use_water_reflection: boolean; use_vegetation: boolean; estimated_speed: string; } // === Elevation grid types === export interface ElevationGridResponse { grid: number[][]; rows: number; cols: number; min_elevation: number; max_elevation: number; bbox: { min_lat: number; max_lat: number; min_lon: number; max_lon: number; }; } // === API Client === class ApiService { private abortController: AbortController | null = null; async getPresets(): Promise> { const response = await fetch(`${API_BASE}/api/coverage/presets`); if (!response.ok) throw new Error('Failed to fetch presets'); const data = await response.json(); return data.presets; } async calculateCoverage(request: CoverageRequest): Promise { // Cancel previous request if running if (this.abortController) { this.abortController.abort(); } this.abortController = new AbortController(); const response = await fetch(`${API_BASE}/api/coverage/calculate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), signal: this.abortController.signal, }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Coverage calculation failed' })); throw new Error(error.detail || 'Coverage calculation failed'); } this.abortController = null; return response.json(); } cancelCalculation() { if (this.abortController) { this.abortController.abort(); this.abortController = null; } } async getElevation(lat: number, lon: number): Promise { const response = await fetch( `${API_BASE}/api/terrain/elevation?lat=${lat}&lon=${lon}` ); if (!response.ok) return 0; const data = await response.json(); return data.elevation; } async getElevationGrid( minLat: number, maxLat: number, minLon: number, maxLon: number, resolution: number = 100, ): Promise { const params = new URLSearchParams({ min_lat: minLat.toString(), max_lat: maxLat.toString(), min_lon: minLon.toString(), max_lon: maxLon.toString(), resolution: resolution.toString(), }); const response = await fetch( `${API_BASE}/api/terrain/elevation-grid?${params}` ); if (!response.ok) throw new Error('Failed to fetch elevation grid'); return response.json(); } // === Region / Caching API === async getRegions(): Promise { const response = await fetch(`${API_BASE}/api/regions/available`); if (!response.ok) throw new Error('Failed to fetch regions'); return response.json(); } async downloadRegion(regionId: string): Promise<{ task_id: string; status: string }> { const response = await fetch(`${API_BASE}/api/regions/download/${regionId}`, { method: 'POST', }); if (!response.ok) throw new Error('Failed to start download'); return response.json(); } async getDownloadProgress(taskId: string): Promise { const response = await fetch(`${API_BASE}/api/regions/download/${taskId}/progress`); if (!response.ok) throw new Error('Failed to get progress'); return response.json(); } async getCacheStats(): Promise { const response = await fetch(`${API_BASE}/api/regions/cache/stats`); if (!response.ok) throw new Error('Failed to get cache stats'); return response.json(); } // === GPU API === async getGPUStatus(): Promise { const response = await fetch(`${API_BASE}/api/gpu/status`); if (!response.ok) throw new Error('Failed to get GPU status'); return response.json(); } async getGPUDevices(): Promise<{ devices: GPUDevice[] }> { const response = await fetch(`${API_BASE}/api/gpu/devices`); if (!response.ok) throw new Error('Failed to get GPU devices'); return response.json(); } async setGPUDevice(backend: string, index: number = 0): Promise<{ status: string; backend: string; device: string }> { const response = await fetch(`${API_BASE}/api/gpu/set`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ backend, index }), }); if (!response.ok) { const err = await response.json().catch(() => ({ detail: 'Failed to set GPU device' })); throw new Error(err.detail || 'Failed to set GPU device'); } return response.json(); } // === Terrain Profile API === async getTerrainProfile( lat1: number, lon1: number, lat2: number, lon2: number, points: number = 100, ): Promise { const params = new URLSearchParams({ lat1: lat1.toString(), lon1: lon1.toString(), lat2: lat2.toString(), lon2: lon2.toString(), points: points.toString(), }); const response = await fetch(`${API_BASE}/api/terrain/profile?${params}`); if (!response.ok) throw new Error('Failed to get terrain profile'); const data = await response.json(); return data.profile ?? data; } } // === Region types === export interface RegionInfo { id: string; name: string; bbox: number[]; srtm_tiles: number; estimated_size_gb: number; downloaded: boolean; download_progress: number; } export interface DownloadProgress { task_id: string; region_id: string; status: string; progress: number; current_step: string; downloaded_mb: number; error?: string; } export interface CacheStats { terrain_mb: number; terrain_tiles: number; buildings_mb: number; water_mb: number; vegetation_mb: number; } // === GPU types === export interface GPUDevice { backend: string; index: number; name: string; memory_mb: number; } export interface GPUStatus { active_backend: string; active_device: GPUDevice | null; gpu_available: boolean; available_devices: GPUDevice[]; } // === Terrain Profile types === export interface TerrainProfilePoint { lat: number; lon: number; elevation: number; distance: number; } export const api = new ApiService();