326 lines
8.4 KiB
TypeScript
326 lines
8.4 KiB
TypeScript
/**
|
|
* 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<Record<string, Preset>> {
|
|
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<CoverageResponse> {
|
|
// 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<number> {
|
|
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<ElevationGridResponse> {
|
|
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<RegionInfo[]> {
|
|
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<DownloadProgress> {
|
|
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<CacheStats> {
|
|
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<GPUStatus> {
|
|
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();
|
|
}
|
|
|
|
async getGPUDiagnostics(): Promise<Record<string, unknown>> {
|
|
const response = await fetch(`${API_BASE}/api/gpu/diagnostics`);
|
|
if (!response.ok) throw new Error('Failed to get GPU diagnostics');
|
|
return response.json();
|
|
}
|
|
|
|
// === Terrain Profile API ===
|
|
|
|
async getTerrainProfile(
|
|
lat1: number, lon1: number,
|
|
lat2: number, lon2: number,
|
|
points: number = 100,
|
|
): Promise<TerrainProfilePoint[]> {
|
|
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();
|