/** * Backend API client for RFCP coverage calculation */ const API_BASE = import.meta.env.VITE_API_URL || 'https://api.rfcp.eliah.one'; // === 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; } // === 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; } } export const api = new ApiService();