Files
rfcp/RFCP-RUST-MIGRATION-PLAN.md
2026-02-07 12:56:25 +02:00

1514 lines
58 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RFCP Rust Migration Plan
## Python/React/Electron → Tauri/SvelteKit/Rust
**Version:** 1.0
**Date:** February 2026
**Status:** Planning
---
## Executive Summary
This document provides a comprehensive migration plan for RFCP (RF Coverage Planner) from the current Python/React/Electron stack to Tauri/SvelteKit/Rust. The migration targets:
- **Performance:** 50km full-preset calculations (currently times out at ~20km)
- **Bundle Size:** 5-15MB (currently 170MB+)
- **Memory:** Constant memory for any radius (currently OOMs on large areas)
---
## Part 1: Current Architecture Analysis
### 1.1 High-Level Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Electron Shell │
│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │
│ │ React Frontend │ │ Python Backend (PyInstaller) │ │
│ │ - Vite/TypeScript │ │ - FastAPI/Uvicorn │ │
│ │ - Leaflet/WebGL │◄──►│ - NumPy/SciPy │ │
│ │ - Zustand stores │HTTP│ - Motor (MongoDB) │ │
│ │ - IndexedDB │ WS │ - ProcessPoolExecutor │ │
│ └─────────────────────┘ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ SRTM │ │ OSM │ │ MongoDB │
│ Tiles │ │ Overpass │ │ │
└──────────┘ └──────────┘ └──────────┘
```
### 1.2 Complete Module Map
#### Backend (`backend/app/`)
```
backend/app/
├── main.py # FastAPI app, CORS, lifespan
├── core/
│ ├── config.py # Pydantic Settings (MONGODB_URL, TERRAIN_DIR)
│ ├── database.py # Motor async MongoDB singleton
│ ├── calculator.py # Legacy calc engine (unused)
│ ├── engine.py # Legacy RF engine (unused)
│ ├── grid.py # Legacy grid utils (unused)
│ └── result.py # Legacy result structs (unused)
├── models/
│ ├── site.py # Site/Sector Pydantic models
│ └── project.py # Project/CoverageSettings models
├── api/
│ ├── deps.py # FastAPI dependencies
│ ├── websocket.py # WebSocket progress handler
│ └── routes/
│ ├── coverage.py # POST /calculate, /preview, /link-budget (1600 lines)
│ ├── terrain.py # GET /elevation, /profile
│ ├── health.py # GET /health
│ ├── projects.py # Project CRUD
│ ├── regions.py # Region management
│ ├── system.py # System info
│ └── gpu.py # GPU detection
├── geometry/
│ ├── haversine.py # Great-circle distance
│ ├── los.py # Line-of-sight with earth curvature
│ ├── diffraction.py # ITU-R P.526 knife-edge
│ ├── intersection.py # Ray-polygon tests
│ └── reflection.py # Specular reflection math
├── propagation/
│ ├── base.py # PropagationModel ABC
│ ├── free_space.py # FSPL model (>2GHz)
│ ├── okumura_hata.py # 150-1500 MHz
│ ├── cost231_hata.py # 1500-2000 MHz (LTE)
│ ├── cost231_wi.py # Street canyon model
│ ├── itu_r_p1546.py # 30-3000 MHz field strength
│ ├── itu_r_p526.py # Knife-edge diffraction
│ └── longley_rice.py # VHF (<150 MHz)
├── services/
│ ├── coverage_service.py # MAIN ORCHESTRATOR (1630 lines)
│ ├── parallel_coverage_service.py # ProcessPool/Ray parallelism
│ ├── terrain_service.py # SRTM loading, mmap, bilinear interp
│ ├── buildings_service.py # OSM buildings fetch/cache
│ ├── los_service.py # Terrain LOS analysis
│ ├── dominant_path_service.py # BOTTLENECK: reflection/diffraction paths
│ ├── materials_service.py # Building penetration loss
│ ├── street_canyon_service.py # Urban canyon propagation
│ ├── reflection_service.py # Single/double bounce
│ ├── vegetation_service.py # Forest attenuation
│ ├── water_service.py # Water reflection gain
│ ├── weather_service.py # Rain attenuation (ITU-R P.838)
│ ├── indoor_service.py # Indoor penetration loss
│ ├── atmospheric_service.py # O2/H2O absorption
│ ├── spatial_index.py # Grid-based building index
│ ├── geometry_vectorized.py # NumPy batch geometry ops
│ ├── gpu_service.py # CuPy/NumPy abstraction
│ ├── gpu_backend.py # GPU array manager
│ ├── cache.py # Generic cache utilities
│ ├── cache_db.py # SQLite cache backend
│ ├── osm_client.py # Overpass API client
│ ├── tile_processor.py # Tiled calculation support
│ └── boundary_service.py # Coverage contour generation
└── utils/
├── logging.py # Logging config
├── progress.py # Progress reporting
└── units.py # Unit conversions
```
#### Frontend (`frontend/src/`)
```
frontend/src/
├── App.tsx # Main app, layout, state wiring
├── main.tsx # React entry point
├── index.css # Tailwind base
├── components/
│ ├── map/
│ │ ├── Map.tsx # Leaflet MapContainer
│ │ ├── SiteMarker.tsx # Site markers with sector wedges
│ │ ├── WebGLRadialCoverageLayer.tsx # GPU radial gradients (NEW)
│ │ ├── WebGLCoverageLayer.tsx # GPU texture interpolation
│ │ ├── GeographicHeatmap.tsx # Canvas fallback
│ │ ├── HeatmapTileRenderer.ts # Tile-based canvas renderer
│ │ ├── CoverageBoundary.tsx # Contour polygon
│ │ ├── ElevationLayer.tsx # Terrain visualization
│ │ ├── ElevationDisplay.tsx # Cursor elevation
│ │ ├── CoordinateGrid.tsx # Lat/lon grid overlay
│ │ ├── MeasurementTool.tsx # Distance measurement
│ │ ├── TerrainProfile.tsx # Elevation profile chart
│ │ ├── LinkBudgetOverlay.tsx # TX-RX link display
│ │ ├── HeatmapLegend.tsx # RSRP color legend
│ │ └── MapExtras.tsx # Miscellaneous overlays
│ ├── panels/
│ │ ├── SiteList.tsx # Site management (19KB)
│ │ ├── BatchEdit.tsx # Batch operations (14KB)
│ │ ├── SiteForm.tsx # Site form component
│ │ ├── LinkBudgetPanel.tsx # Link budget analysis
│ │ ├── CoverageStats.tsx # Calculation statistics
│ │ ├── ResultsPanel.tsx # Results summary
│ │ ├── HistoryPanel.tsx # Calculation history
│ │ ├── ProjectPanel.tsx # Project save/load
│ │ ├── FrequencyBandPanel.tsx # Frequency presets
│ │ ├── FrequencySelector.tsx # Frequency input
│ │ ├── BatchFrequencyChange.tsx # Batch frequency edit
│ │ ├── SiteImportExport.tsx # Import/export
│ │ └── ExportPanel.tsx # KML/GeoJSON export
│ ├── modals/
│ │ ├── SiteConfigModal.tsx # Create/edit site dialog
│ │ ├── ModalBackdrop.tsx # Modal backdrop
│ │ └── index.ts # Modal exports
│ └── ui/
│ ├── Button.tsx # Styled button
│ ├── Input.tsx # Text input
│ ├── NumberInput.tsx # Numeric input
│ ├── Toast.tsx # Toast notifications
│ ├── ThemeToggle.tsx # Dark/light mode
│ ├── GPUIndicator.tsx # GPU status
│ └── ConfirmDialog.tsx # Confirmation modal
├── store/
│ ├── sites.ts # Site CRUD + IndexedDB
│ ├── coverage.ts # Coverage calculation state
│ ├── settings.ts # UI/map preferences
│ ├── history.ts # Undo/redo stack
│ ├── projects.ts # Project management
│ ├── calcHistory.ts # Calculation history
│ └── tools.ts # Active tool state
├── services/
│ ├── api.ts # HTTP API client
│ ├── websocket.ts # WebSocket client
│ └── terrain.ts # Terrain utilities
├── hooks/
│ ├── useWebSocket.ts # WS connection hook
│ ├── useKeyboardShortcuts.ts # Keyboard shortcuts
│ ├── useElevation.ts # Elevation queries
│ └── useUnsavedChanges.ts # Unsaved changes warning
├── types/
│ ├── site.ts # Site interfaces
│ ├── coverage.ts # Coverage interfaces
│ └── frequency.ts # Frequency types
├── rf/
│ ├── fspl.ts # Free-space path loss
│ ├── antenna-pattern.ts # Antenna gain patterns
│ ├── calculator.ts # Client-side RF utils
│ └── utils.ts # RF helpers
├── db/
│ └── schema.ts # Dexie IndexedDB schema
├── lib/
│ └── desktop.ts # Electron IPC bridge
├── constants/
│ ├── frequencies.ts # Frequency band presets
│ └── rsrp-thresholds.ts # Signal thresholds
└── utils/
├── logger.ts # Logging utility
├── geographicScale.ts # Geographic calculations
└── colorGradient.ts # Color utilities
```
#### Desktop (`desktop/`)
```
desktop/
├── main.js # Electron main process
├── preload.js # IPC bridge
├── splash.html # Loading screen
├── package.json # Electron-builder config
└── assets/
├── icon.ico # Windows icon
├── icon.png # Linux icon
└── icon.icns # macOS icon
```
### 1.3 Data Flow Diagram
```
┌──────────────────────────────────────────────────────────────────────────────┐
│ Coverage Calculation Pipeline │
└──────────────────────────────────────────────────────────────────────────────┘
User clicks "Calculate"
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ WebSocket or │ │ Backend │
│ useCoverageStore│────►│ HTTP POST │────►│ /api/coverage/ │
│ .calculate() │ │ │ │ calculate │
└─────────────────┘ └─────────────────┘ └────────┬────────┘
┌────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ CoverageService.calculate() │
│ │
│ 1. apply_preset() ─────────────────────────┐ │
│ │ │
│ 2. _fetch_osm_grid_aligned() │ │
│ ├── buildings_service.fetch_buildings() │ │
│ ├── street_canyon_service.fetch_streets()│ │
│ ├── water_service.fetch_water_bodies() │ │
│ └── vegetation_service.fetch_areas() │ │
│ │ │
│ 3. terrain_service.ensure_tiles() │ │
│ │ │
│ 4. GPU precomputation (if available) │ │
│ ├── precompute_distances() │ │
│ ├── precompute_path_loss() │ │
│ ├── batch_terrain_los() │ │
│ └── batch_antenna_pattern() │ │
│ │ │
│ 5. Point loop (parallel or sequential) │ │
│ │ │ │
│ │ For each grid point: │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ a. Use precomputed distance │ │ │
│ │ │ b. Use precomputed path_loss │ │ │
│ │ │ c. Use precomputed antenna_loss │ │ │
│ │ │ d. Building LOS check │◄──┼──┼── BOTTLENECK
│ │ │ e. Material penetration loss │ │ │
│ │ │ f. Dominant path analysis │◄──┼──┼── BOTTLENECK
│ │ │ g. Street canyon loss │ │ │
│ │ │ h. Vegetation attenuation │ │ │
│ │ │ i. Reflection paths │ │ │
│ │ │ j. Water reflection │ │ │
│ │ │ k. Rain attenuation │ │ │
│ │ │ l. Indoor penetration │ │ │
│ │ │ m. Atmospheric loss │ │ │
│ │ │ n. Combine all losses │ │ │
│ │ │ o. Final RSRP │ │ │
│ │ └─────────────────────────────────┘ │ │
│ │ │ │
│ └── Parallel: ProcessPoolExecutor │ │
│ - 4-6 workers │ │
│ - Data pickled per worker │ │
│ - ~100MB pickle overhead each │ │
│ │ │
│ 6. Collect results, compute stats │ │
└──────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ CoverageResult │
│ - points: CoveragePoint[] (lat, lon, rsrp, ...) │
│ - stats: min/max/avg, LOS%, model distribution │
│ - computation_time, models_used │
│ - boundary: contour polygon │
└────────────────────────┬────────────────────────┘
┌─────────────┘
┌─────────────────────────────────────────────────┐
│ WebGL Rendering │
│ │
│ WebGLRadialCoverageLayer (preferred): │
│ - Pass 1: Point accumulation (Gaussian radial) │
│ - Pass 2: Normalize + colormap │
│ - Instanced rendering: 1 draw call for all pts │
│ │
│ WebGLCoverageLayer (texture): │
│ - Grid to texture │
│ - Quintic Hermite interpolation │
│ - Single-pass colormap │
└─────────────────────────────────────────────────┘
```
### 1.4 Performance Bottleneck Analysis
#### Critical Bottlenecks (Line-Level Detail)
**1. Dominant Path Service (`dominant_path_service.py:155-350`)**
The `find_dominant_paths_vectorized()` function is the #1 bottleneck:
```python
# Line 208-214: Building filtering per point
line_buildings = _filter_buildings_by_distance(
line_buildings,
(tx_lat, tx_lon), (rx_lat, rx_lon),
max_count=MAX_BUILDINGS_FOR_LINE, # 30
max_distance=MAX_DISTANCE_FROM_PATH, # 200m
)
# Line 230-234: Building to array conversion (allocates new arrays per point!)
walls_start, walls_end, wall_to_bldg, poly_x, poly_y, poly_lengths = (
_buildings_to_arrays(line_buildings, ref_lat, ref_lon)
)
# Line 300-307: Vectorized reflection search (still expensive)
refl_point, refl_length, refl_loss = find_best_reflection_path_vectorized(
tx, rx, r_walls_start, r_walls_end, r_wall_to_bldg,
r_poly_x, r_poly_y, r_poly_lengths,
max_candidates=30, max_walls=100, max_los_checks=5,
)
```
**Timing breakdown (from logs):**
- Building query: 0.5-2ms per point
- Array conversion: 1-5ms per point
- LOS polygon test: 0.5-2ms per point
- Reflection search: 5-20ms per point (when LOS blocked)
- **Total: 10-30ms per point with buildings**
For 6,700 points (50m resolution, 10km): **67-200 seconds just for dominant path**
**2. ProcessPoolExecutor Overhead (`parallel_coverage_service.py`)**
```python
# Each worker receives a pickled copy of:
# - buildings list: 10-50MB (5000+ buildings with geometry)
# - terrain cache: Referenced but requires re-mmap
# - vegetation areas: 1-10MB
# - water bodies: 1-5MB
# Line 180-195: Data serialization
chunk_data = {
'grid_chunk': grid_points[start:end],
'site_params': site_params,
'settings': settings,
'buildings': buildings, # FULL COPY
'streets': streets,
'water_bodies': water_bodies,
'vegetation_areas': vegetation_areas,
}
```
**Memory impact:** 4 workers × 100MB pickle = 400MB overhead just for parallelism
**3. Terrain Service (`terrain_service.py:180-232`)**
Bilinear interpolation is called per point:
```python
def _bilinear_sample(self, tile: np.ndarray, lat: float, lon: float) -> float:
# 20+ float operations per sample
# Not vectorized - called in Python loop
```
For terrain LOS checks: 50 samples × 6,700 points = 335,000 calls
**4. Building Intersection (`dominant_path_service.py:674-690`)**
```python
def _line_intersects_building_3d(self, ...):
# Line 682: 20 samples per line segment
for t in np.linspace(0, 1, 20):
# Point-in-polygon test per sample
if buildings_service.point_in_building(lat, lon, building):
if height < building.height:
return True
```
**5. Python GIL and Async Overhead**
- ProcessPoolExecutor requires pickling all data
- asyncio event loop adds latency for every await
- NumPy releases GIL but building intersection code doesn't
### 1.5 Memory Usage Patterns
| Component | Memory (10km) | Memory (50km) | Notes |
|-----------|--------------|---------------|-------|
| Grid points | 2MB | 50MB | 6,700 → 167,000 points |
| SRTM tiles | 25-100MB | 100-400MB | 1-16 tiles × 25MB |
| Buildings | 10-100MB | 50-500MB | Geometry arrays |
| Worker copies | 100-400MB | 400-2GB | Pickle per worker |
| GPU buffers | 50-200MB | 200-800MB | CuPy arrays |
| **Total** | **200-800MB** | **800MB-4GB** | OOM likely at 50km |
---
## Part 2: Target Architecture
### 2.1 Rust Backend Structure
```
src-tauri/
├── Cargo.toml # Workspace + deps
├── src/
│ ├── main.rs # Tauri app entry
│ ├── lib.rs # Library exports
│ ├── commands/ # Tauri IPC commands
│ │ ├── mod.rs
│ │ ├── coverage.rs # #[tauri::command] calculate_coverage
│ │ ├── terrain.rs # Elevation queries
│ │ ├── projects.rs # Project CRUD
│ │ └── system.rs # System info, GPU detection
│ │
│ ├── coverage/ # Coverage calculation engine
│ │ ├── mod.rs
│ │ ├── engine.rs # Main calculation orchestrator
│ │ ├── grid.rs # Grid generation (adaptive)
│ │ ├── point.rs # Per-point calculation
│ │ ├── precompute.rs # Vectorized precomputation
│ │ └── result.rs # CoveragePoint, CoverageResult
│ │
│ ├── propagation/ # RF propagation models
│ │ ├── mod.rs
│ │ ├── traits.rs # PropagationModel trait
│ │ ├── free_space.rs # FSPL
│ │ ├── okumura_hata.rs # 150-1500 MHz
│ │ ├── cost231_hata.rs # 1500-2000 MHz
│ │ ├── cost231_wi.rs # Street canyon
│ │ ├── itu_r_p1546.rs # Field strength model
│ │ ├── longley_rice.rs # VHF
│ │ └── knife_edge.rs # ITU-R P.526 diffraction
│ │
│ ├── geometry/ # Geometry operations
│ │ ├── mod.rs
│ │ ├── haversine.rs # Great-circle distance (SIMD)
│ │ ├── los.rs # Line-of-sight (vectorized)
│ │ ├── intersection.rs # Ray-polygon (SIMD)
│ │ ├── reflection.rs # Specular reflection
│ │ └── spatial_index.rs # R-tree or grid index
│ │
│ ├── terrain/ # Terrain data management
│ │ ├── mod.rs
│ │ ├── srtm.rs # SRTM tile loading (mmap)
│ │ ├── cache.rs # LRU tile cache
│ │ ├── sample.rs # Bilinear interpolation (SIMD)
│ │ └── profile.rs # Elevation profiles
│ │
│ ├── osm/ # OpenStreetMap data
│ │ ├── mod.rs
│ │ ├── client.rs # Overpass API client
│ │ ├── building.rs # Building struct + parsing
│ │ ├── cache.rs # SQLite OSM cache
│ │ └── spatial.rs # Spatial queries
│ │
│ ├── environment/ # Environmental effects
│ │ ├── mod.rs
│ │ ├── materials.rs # Building materials/penetration
│ │ ├── vegetation.rs # Forest attenuation
│ │ ├── water.rs # Water reflection
│ │ ├── weather.rs # Rain attenuation
│ │ ├── indoor.rs # Indoor penetration
│ │ └── atmospheric.rs # O2/H2O absorption
│ │
│ ├── gpu/ # GPU compute (optional)
│ │ ├── mod.rs
│ │ ├── wgpu_backend.rs # wgpu compute shaders
│ │ ├── shaders/
│ │ │ ├── propagation.wgsl # Path loss compute
│ │ │ ├── terrain_los.wgsl # Batch LOS check
│ │ │ └── antenna.wgsl # Antenna pattern
│ │ └── fallback.rs # CPU SIMD fallback
│ │
│ ├── db/ # Data persistence
│ │ ├── mod.rs
│ │ ├── sqlite.rs # SQLite for projects/cache
│ │ └── schema.rs # Database schema
│ │
│ └── utils/
│ ├── mod.rs
│ ├── progress.rs # Progress reporting
│ └── units.rs # Unit conversions
└── build.rs # Build script (shader compilation)
```
### 2.2 SvelteKit Frontend Structure
```
src/
├── app.html # HTML template
├── app.css # Global styles (Tailwind)
├── routes/
│ ├── +layout.svelte # Root layout
│ └── +page.svelte # Main app page
├── lib/
│ ├── components/
│ │ ├── map/
│ │ │ ├── MapContainer.svelte # Leaflet wrapper
│ │ │ ├── SiteMarker.svelte # Site markers
│ │ │ ├── CoverageLayer.svelte # WebGL coverage (port shaders)
│ │ │ ├── ElevationLayer.svelte
│ │ │ ├── TerrainProfile.svelte
│ │ │ ├── MeasurementTool.svelte
│ │ │ └── Legend.svelte
│ │ │
│ │ ├── panels/
│ │ │ ├── SiteList.svelte # Site management
│ │ │ ├── BatchEdit.svelte # Batch operations
│ │ │ ├── Settings.svelte # Coverage settings
│ │ │ ├── LinkBudget.svelte # Link budget analysis
│ │ │ ├── History.svelte # Calculation history
│ │ │ └── Export.svelte # Export options
│ │ │
│ │ ├── modals/
│ │ │ ├── SiteConfig.svelte # Site edit modal
│ │ │ └── Confirm.svelte # Confirmation dialog
│ │ │
│ │ └── ui/
│ │ ├── Button.svelte
│ │ ├── Input.svelte
│ │ ├── Select.svelte
│ │ ├── Toast.svelte
│ │ └── ThemeToggle.svelte
│ │
│ ├── stores/
│ │ ├── sites.ts # Site state (Svelte stores)
│ │ ├── coverage.ts # Coverage state
│ │ ├── settings.ts # UI settings
│ │ ├── history.ts # Undo/redo
│ │ └── projects.ts # Project state
│ │
│ ├── tauri/
│ │ ├── commands.ts # Tauri command wrappers
│ │ ├── events.ts # Event listeners
│ │ └── types.ts # Shared types
│ │
│ ├── webgl/
│ │ ├── radial-layer.ts # WebGL radial gradients (port)
│ │ ├── texture-layer.ts # WebGL texture (port)
│ │ └── shaders.ts # GLSL shaders (reuse)
│ │
│ ├── rf/
│ │ ├── fspl.ts # Client-side FSPL
│ │ └── antenna.ts # Antenna patterns
│ │
│ └── utils/
│ ├── colors.ts # Color utilities
│ └── geo.ts # Geographic helpers
├── static/
│ └── icons/ # App icons
└── tests/
└── ...
```
### 2.3 Tauri IPC Design
#### Commands (Rust → JS)
```rust
// src-tauri/src/commands/coverage.rs
#[tauri::command]
async fn calculate_coverage(
app: AppHandle,
sites: Vec<SiteParams>,
settings: CoverageSettings,
) -> Result<CoverageResult, String> {
// Spawns calculation on Rayon thread pool
// Emits progress events via app.emit_all()
}
#[tauri::command]
fn cancel_coverage(app: AppHandle) -> Result<(), String> {
// Sets cancellation flag
}
#[tauri::command]
async fn get_elevation(lat: f64, lon: f64) -> Result<f64, String> {
// Single point elevation
}
#[tauri::command]
async fn get_elevation_profile(
lat1: f64, lon1: f64,
lat2: f64, lon2: f64,
num_points: u32,
) -> Result<Vec<ElevationPoint>, String> {
// Terrain profile
}
#[tauri::command]
async fn fetch_osm_data(
min_lat: f64, min_lon: f64,
max_lat: f64, max_lon: f64,
layers: Vec<String>,
) -> Result<OsmData, String> {
// Pre-fetch buildings, water, vegetation
}
```
#### Events (JS → Rust progress updates)
```typescript
// src/lib/tauri/events.ts
import { listen } from '@tauri-apps/api/event';
interface ProgressPayload {
phase: string;
progress: number; // 0.0-1.0
eta_seconds?: number;
tile?: { current: number; total: number };
}
interface PartialResultPayload {
points: CoveragePoint[];
tile_index: number;
}
export function listenCoverageProgress(
onProgress: (p: ProgressPayload) => void,
onPartial: (p: PartialResultPayload) => void,
) {
const unlisten1 = listen<ProgressPayload>('coverage:progress', e => onProgress(e.payload));
const unlisten2 = listen<PartialResultPayload>('coverage:partial', e => onPartial(e.payload));
return () => { unlisten1.then(f => f()); unlisten2.then(f => f()); };
}
```
#### Data Flow
```
┌─────────────────────────────────────────────────────────────┐
│ SvelteKit Frontend │
│ │
│ coverageStore.calculate() │
│ │ │
│ ▼ │
│ invoke('calculate_coverage', { sites, settings }) │
│ │ │
│ listen('coverage:progress', updateProgress) │
│ listen('coverage:partial', addPartialPoints) │
│ │ │
└────────┼─────────────────────────────────────────────────────┘
│ Tauri IPC (JSON serialization, ~1ms)
┌─────────────────────────────────────────────────────────────┐
│ Rust Backend │
│ │
│ #[tauri::command] │
│ async fn calculate_coverage(...) { │
│ // Spawn on Rayon thread pool │
│ rayon::spawn(move || { │
│ // Parallel grid processing │
│ grid_points.par_chunks(256) │
│ .for_each(|chunk| { │
│ // SIMD-optimized point calculation │
│ let results = calculate_chunk(chunk); │
│ // Emit partial results │
│ app.emit_all("coverage:partial", results); │
│ }); │
│ }); │
│ } │
└─────────────────────────────────────────────────────────────┘
```
### 2.4 Data Management Strategy
#### Terrain (SRTM)
```rust
// Memory-mapped tiles with zero-copy access
pub struct TerrainManager {
tiles: HashMap<TileKey, Mmap>, // Direct mmap, no copy
lru: LruCache<TileKey, ()>, // LRU tracking only
}
impl TerrainManager {
pub fn get_elevation(&self, lat: f64, lon: f64) -> f32 {
let tile = self.get_tile(lat, lon)?;
// Direct pointer arithmetic on mmap'd data
// SIMD-optimized bilinear interpolation
simd_bilinear_sample(tile.as_ptr(), lat, lon)
}
pub fn get_elevations_batch(&self, coords: &[(f64, f64)]) -> Vec<f32> {
// Process 8 points at a time with AVX2
coords.par_chunks(8)
.flat_map(|chunk| simd_batch_sample(chunk))
.collect()
}
}
```
#### OSM Data
```rust
// SQLite cache with FlatBuffers serialization
pub struct OsmCache {
db: Connection, // rusqlite
}
impl OsmCache {
pub fn get_buildings(&self, bbox: BBox) -> Vec<Building> {
// Query by spatial index
// Deserialize with FlatBuffers (zero-copy)
}
pub fn get_or_fetch(&self, bbox: BBox) -> Vec<Building> {
if let Some(cached) = self.get_buildings(bbox) {
return cached;
}
// Fetch from Overpass, cache, return
}
}
```
#### Projects
```rust
// SQLite for projects (simple, portable)
pub struct ProjectStore {
db: Connection,
}
// Schema
CREATE TABLE projects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
data BLOB, -- MessagePack serialized
created_at INTEGER,
updated_at INTEGER
);
CREATE TABLE sites (
id TEXT PRIMARY KEY,
project_id TEXT,
data BLOB,
FOREIGN KEY (project_id) REFERENCES projects(id)
);
```
---
## Part 3: Migration Phases
### Phase 1: Tauri + SvelteKit Shell (Replace Electron)
**Goal:** Working desktop app with same UI, backend still Python
**Duration:** 2-3 weeks
#### Tasks
1. **Initialize Tauri project**
```bash
npm create tauri-app@latest rfcp-tauri -- --template svelte-ts
```
2. **Port UI components to Svelte**
- Start with layout (App.tsx → +page.svelte)
- Port UI components (Button, Input, etc.)
- Port map components (reuse Leaflet)
- Port panels (SiteList, Settings, etc.)
3. **Port WebGL layers**
- Copy GLSL shaders verbatim
- Adapt TypeScript to Svelte (minimal changes)
- WebGLRadialCoverageLayer → radial-layer.ts
4. **Implement Tauri commands as HTTP proxies**
```rust
#[tauri::command]
async fn calculate_coverage(...) -> Result<Value, String> {
// Forward to Python backend via HTTP
let response = reqwest::get("http://localhost:8090/api/coverage/calculate")
.await?;
Ok(response.json().await?)
}
```
5. **Port state management**
- Zustand → Svelte stores
- Keep same persistence (IndexedDB via idb)
6. **Test parity**
- All features working
- Same performance as Electron version
**Deliverable:** RFCP desktop app on Tauri, Python backend, ~20MB bundle
---
### Phase 2: Rust Propagation Engine (The Big Win)
**Goal:** RF calculations in Rust, 50-100x faster for geometry-heavy operations
**Duration:** 4-6 weeks
#### Tasks
1. **Core geometry in Rust**
```rust
// Haversine with SIMD (8 distances per instruction)
pub fn haversine_batch(
site: (f64, f64),
points: &[(f64, f64)],
) -> Vec<f64> {
points.par_chunks(8)
.flat_map(|chunk| simd_haversine(site, chunk))
.collect()
}
// Line-polygon intersection with SIMD
pub fn line_intersects_polygon_batch(
line: (Point, Point),
polygons: &[Polygon],
) -> Vec<bool> {
// Vectorized edge tests
}
```
2. **Propagation models**
```rust
pub trait PropagationModel: Send + Sync {
fn calculate(&self, input: &PropagationInput) -> PropagationOutput;
fn calculate_batch(&self, inputs: &[PropagationInput]) -> Vec<PropagationOutput>;
}
// FSPL: trivial, ~1 nanosecond per point
// Okumura-Hata: ~10 nanoseconds per point
// COST-231: ~15 nanoseconds per point
```
3. **Dominant path in Rust**
```rust
pub fn find_dominant_path(
tx: Point3D,
rx: Point3D,
buildings: &SpatialIndex<Building>,
) -> DominantPath {
// R-tree query: O(log n)
let nearby = buildings.query_line(tx.to_2d(), rx.to_2d());
// SIMD polygon tests
let hits = simd_line_polygon_batch(&nearby, tx, rx);
// Vectorized reflection search
if !hits.is_empty() {
find_best_reflection_simd(&nearby, tx, rx)
} else {
DominantPath::Direct
}
}
```
4. **Parallel grid processing**
```rust
pub fn calculate_coverage(
sites: &[Site],
settings: &CoverageSettings,
on_progress: impl Fn(f32),
) -> CoverageResult {
let grid = generate_grid(sites, settings);
// Rayon parallel iteration
let points: Vec<CoveragePoint> = grid
.par_iter()
.enumerate()
.map(|(i, coord)| {
if i % 1000 == 0 {
on_progress(i as f32 / grid.len() as f32);
}
calculate_point(coord, sites, settings)
})
.collect();
CoverageResult { points, stats: compute_stats(&points) }
}
```
5. **Benchmark against Python**
- Target: 10x faster for basic (terrain only)
- Target: 50x faster for full (buildings + dominant path)
**Deliverable:** Rust coverage engine, 50km calculations possible
---
### Phase 3: Rust Terrain/OSM Services (Offline-First)
**Goal:** Complete data layer in Rust, no Python dependency
**Duration:** 3-4 weeks
#### Tasks
1. **Terrain service**
```rust
pub struct TerrainService {
data_dir: PathBuf,
tiles: RwLock<HashMap<TileKey, Mmap>>,
download_queue: Mutex<VecDeque<TileKey>>,
}
impl TerrainService {
pub async fn ensure_tiles(&self, bbox: BBox) -> Result<()> {
let needed = self.get_needed_tiles(bbox);
for tile in needed {
if !self.has_tile(tile) {
self.download_tile(tile).await?;
}
}
Ok(())
}
}
```
2. **OSM client with caching**
```rust
pub struct OsmService {
client: reqwest::Client,
cache: SqliteCache,
}
impl OsmService {
pub async fn get_buildings(&self, bbox: BBox) -> Vec<Building> {
if let Some(cached) = self.cache.get_buildings(bbox) {
return cached;
}
let response = self.client
.post("https://overpass-api.de/api/interpreter")
.body(overpass_query(bbox, "building"))
.send()
.await?;
let buildings = parse_overpass_response(response);
self.cache.store_buildings(bbox, &buildings);
buildings
}
}
```
3. **Spatial indexing**
```rust
// R-tree for fast spatial queries
pub struct BuildingIndex {
rtree: RTree<BuildingRef>,
}
impl BuildingIndex {
pub fn query_line(&self, p1: Point, p2: Point) -> Vec<&Building> {
let envelope = LineString::new(vec![p1, p2]).envelope();
self.rtree
.locate_in_envelope(&envelope)
.filter(|b| b.geometry.intersects_line(p1, p2))
.collect()
}
}
```
4. **Project persistence**
- SQLite database for projects
- Binary serialization (MessagePack or bincode)
5. **Remove Python backend**
- All functionality in Rust
- Single executable
**Deliverable:** Self-contained Rust app, no Python, ~10MB bundle
---
### Phase 4: Advanced Features (GPU Compute, 50km+)
**Goal:** Handle massive calculations with GPU acceleration
**Duration:** 4-6 weeks
#### Tasks
1. **wgpu compute shaders**
```wgsl
// propagation.wgsl
@group(0) @binding(0) var<storage, read> grid_points: array<vec2<f32>>;
@group(0) @binding(1) var<storage, read> site: vec4<f32>; // lat, lon, height, freq
@group(0) @binding(2) var<storage, read_write> path_loss: array<f32>;
@compute @workgroup_size(256)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
let idx = id.x;
if (idx >= arrayLength(&grid_points)) { return; }
let point = grid_points[idx];
let dist = haversine(site.xy, point);
// COST-231 Hata
let freq = site.w;
let height = site.z;
path_loss[idx] = cost231_hata(dist, freq, height);
}
```
2. **Tiled GPU processing**
```rust
pub async fn calculate_coverage_gpu(
sites: &[Site],
settings: &CoverageSettings,
gpu: &GpuContext,
) -> CoverageResult {
let grid = generate_grid(sites, settings);
// Process in tiles that fit in GPU memory
let tile_size = 50_000; // 50k points per tile
let mut all_points = Vec::new();
for tile in grid.chunks(tile_size) {
// Upload to GPU
let grid_buffer = gpu.create_buffer(tile);
// Dispatch compute shader
gpu.dispatch_propagation(grid_buffer, sites);
gpu.dispatch_terrain_los(grid_buffer, terrain);
// Read back results
let results = gpu.read_buffer(grid_buffer);
all_points.extend(results);
}
CoverageResult { points: all_points, ... }
}
```
3. **Streaming results**
- Emit partial results per tile
- Progressive rendering in UI
4. **Memory-mapped result caching**
- Cache calculation results
- Instant re-render on settings change
**Deliverable:** 50km+ calculations, GPU-accelerated, <30 second full preset
---
## Part 4: Component Migration Map
### Backend Services
| Current File | Action | Target | Notes |
|-------------|--------|--------|-------|
| `coverage_service.py` | REWRITE | `src-tauri/src/coverage/engine.rs` | Rayon parallelism, SIMD geometry |
| `parallel_coverage_service.py` | DELETE | N/A | Replaced by Rayon |
| `terrain_service.py` | REWRITE | `src-tauri/src/terrain/srtm.rs` | mmap + SIMD bilinear |
| `buildings_service.py` | REWRITE | `src-tauri/src/osm/building.rs` | R-tree index |
| `los_service.py` | REWRITE | `src-tauri/src/geometry/los.rs` | Vectorized LOS |
| `dominant_path_service.py` | REWRITE | `src-tauri/src/coverage/dominant_path.rs` | SIMD intersection tests |
| `materials_service.py` | REWRITE | `src-tauri/src/environment/materials.rs` | Direct port |
| `street_canyon_service.py` | REWRITE | `src-tauri/src/environment/street_canyon.rs` | Direct port |
| `reflection_service.py` | REWRITE | `src-tauri/src/geometry/reflection.rs` | SIMD specular calc |
| `vegetation_service.py` | REWRITE | `src-tauri/src/environment/vegetation.rs` | Direct port |
| `water_service.py` | REWRITE | `src-tauri/src/environment/water.rs` | Direct port |
| `weather_service.py` | REWRITE | `src-tauri/src/environment/weather.rs` | Direct port |
| `indoor_service.py` | REWRITE | `src-tauri/src/environment/indoor.rs` | Direct port (trivial) |
| `atmospheric_service.py` | REWRITE | `src-tauri/src/environment/atmospheric.rs` | Direct port |
| `spatial_index.py` | REWRITE | `src-tauri/src/geometry/spatial_index.rs` | Use `rstar` crate |
| `geometry_vectorized.py` | REWRITE | `src-tauri/src/geometry/intersection.rs` | SIMD with `packed_simd2` |
| `gpu_service.py` | REWRITE | `src-tauri/src/gpu/wgpu_backend.rs` | wgpu compute shaders |
| `osm_client.py` | REWRITE | `src-tauri/src/osm/client.rs` | reqwest + serde |
| `cache.py` | REWRITE | `src-tauri/src/db/sqlite.rs` | rusqlite |
### Propagation Models
| Current File | Action | Target | Notes |
|-------------|--------|--------|-------|
| `free_space.py` | REWRITE | `src-tauri/src/propagation/free_space.rs` | Trivial port |
| `okumura_hata.py` | REWRITE | `src-tauri/src/propagation/okumura_hata.rs` | Same formulas |
| `cost231_hata.py` | REWRITE | `src-tauri/src/propagation/cost231_hata.rs` | Same formulas |
| `cost231_wi.py` | REWRITE | `src-tauri/src/propagation/cost231_wi.rs` | Same formulas |
| `itu_r_p1546.py` | REWRITE | `src-tauri/src/propagation/itu_r_p1546.rs` | Same formulas |
| `longley_rice.py` | REWRITE | `src-tauri/src/propagation/longley_rice.rs` | Same formulas |
| `itu_r_p526.py` | REWRITE | `src-tauri/src/propagation/knife_edge.rs` | Same formulas |
### Geometry
| Current File | Action | Target | Notes |
|-------------|--------|--------|-------|
| `geometry/haversine.py` | REWRITE | `src-tauri/src/geometry/haversine.rs` | SIMD batch version |
| `geometry/los.py` | REWRITE | `src-tauri/src/geometry/los.rs` | Vectorized |
| `geometry/diffraction.py` | REWRITE | `src-tauri/src/geometry/diffraction.rs` | Same formulas |
| `geometry/intersection.py` | REWRITE | `src-tauri/src/geometry/intersection.rs` | SIMD |
| `geometry/reflection.py` | REWRITE | `src-tauri/src/geometry/reflection.rs` | SIMD |
### Frontend Components
| Current File | Action | Target | Notes |
|-------------|--------|--------|-------|
| `App.tsx` | PORT | `src/routes/+page.svelte` | Svelte syntax |
| `components/map/Map.tsx` | PORT | `src/lib/components/map/MapContainer.svelte` | Svelte + Leaflet |
| `components/map/WebGLRadialCoverageLayer.tsx` | PORT | `src/lib/webgl/radial-layer.ts` | Keep shaders verbatim |
| `components/map/WebGLCoverageLayer.tsx` | PORT | `src/lib/webgl/texture-layer.ts` | Keep shaders verbatim |
| `components/map/SiteMarker.tsx` | PORT | `src/lib/components/map/SiteMarker.svelte` | Svelte syntax |
| `components/map/*.tsx` | PORT | `src/lib/components/map/*.svelte` | Svelte syntax |
| `components/panels/SiteList.tsx` | PORT | `src/lib/components/panels/SiteList.svelte` | Svelte syntax |
| `components/panels/*.tsx` | PORT | `src/lib/components/panels/*.svelte` | Svelte syntax |
| `components/modals/*.tsx` | PORT | `src/lib/components/modals/*.svelte` | Svelte syntax |
| `components/ui/*.tsx` | PORT | `src/lib/components/ui/*.svelte` | Svelte syntax |
| `store/sites.ts` | PORT | `src/lib/stores/sites.ts` | Svelte stores |
| `store/coverage.ts` | PORT | `src/lib/stores/coverage.ts` | + Tauri commands |
| `store/*.ts` | PORT | `src/lib/stores/*.ts` | Svelte stores |
| `services/api.ts` | REWRITE | `src/lib/tauri/commands.ts` | Tauri invoke() |
| `services/websocket.ts` | REWRITE | `src/lib/tauri/events.ts` | Tauri events |
| `hooks/*.ts` | PORT | `src/lib/hooks/*.ts` | Svelte stores |
| `rf/*.ts` | PORT | `src/lib/rf/*.ts` | Direct copy |
| `types/*.ts` | PORT | `src/lib/tauri/types.ts` | Merge + simplify |
### Desktop
| Current File | Action | Target | Notes |
|-------------|--------|--------|-------|
| `desktop/main.js` | DELETE | N/A | Tauri handles this |
| `desktop/preload.js` | DELETE | N/A | Tauri IPC |
| `desktop/splash.html` | PORT | `src-tauri/src/splashscreen.rs` | Optional |
| Electron-builder config | REWRITE | `src-tauri/tauri.conf.json` | Tauri config |
---
## Part 5: Risk Assessment
### 5.1 Hard to Port
| Component | Difficulty | Risk | Mitigation |
|-----------|------------|------|------------|
| Dominant path geometry | High | Complex vectorization | Extensive testing, benchmark vs Python |
| SRTM bilinear interp | Medium | Numerical precision | Unit tests with known values |
| Overpass API parsing | Medium | OSM data quirks | Use established crate (osmpbf) |
| WebGL shaders | Low | Already GLSL | Copy verbatim |
| Leaflet integration | Low | Same as React | svelte-leaflet exists |
### 5.2 Needs R&D
| Topic | Effort | Notes |
|-------|--------|-------|
| wgpu compute shaders | 2-3 weeks | Learn wgpu, write propagation shaders |
| SIMD geometry | 1-2 weeks | Use `packed_simd2` or `std::simd` |
| R-tree spatial index | 1 week | Evaluate `rstar` vs custom |
| SQLite FTS for OSM | 1 week | May need RTree extension |
### 5.3 Minimum Viable Product (MVP)
**Phase 1 MVP (Tauri shell):**
- SvelteKit UI with Leaflet
- WebGL coverage rendering
- Site management (CRUD, import/export)
- Python backend via HTTP proxy
- ~20MB bundle
**Phase 2 MVP (Rust engine):**
- Basic propagation (FSPL, Okumura-Hata, COST-231)
- Terrain LOS (no buildings yet)
- 10km calculations in <10 seconds
- ~12MB bundle
**Phase 3 MVP (Full Rust):**
- Buildings + dominant path
- All propagation models
- 20km full preset in <30 seconds
- ~10MB bundle
### 5.4 Estimated Timeline
| Phase | Duration | Cumulative | Dependencies |
|-------|----------|------------|--------------|
| Phase 1: Tauri Shell | 2-3 weeks | 3 weeks | None |
| Phase 2: Rust Engine | 4-6 weeks | 9 weeks | Phase 1 |
| Phase 3: Full Rust | 3-4 weeks | 13 weeks | Phase 2 |
| Phase 4: GPU/50km | 4-6 weeks | 19 weeks | Phase 3 |
**Total: ~4-5 months for full migration**
### 5.5 Key Dependencies (Rust Crates)
```toml
[dependencies]
# Tauri
tauri = { version = "1.5", features = ["api-all"] }
tauri-plugin-store = "0.1"
# Async runtime
tokio = { version = "1.0", features = ["full"] }
# HTTP client
reqwest = { version = "0.11", features = ["json"] }
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rmp-serde = "1.1" # MessagePack
# Database
rusqlite = { version = "0.30", features = ["bundled"] }
# Spatial
rstar = "0.11" # R-tree
geo = "0.27" # Geometry types
# SIMD
packed_simd2 = "0.3" # or std::simd on nightly
# GPU (optional)
wgpu = "0.18"
# Parallelism
rayon = "1.8"
# Memory mapping
memmap2 = "0.9"
# Logging
tracing = "0.1"
tracing-subscriber = "0.3"
```
### 5.6 Performance Targets
| Scenario | Python Current | Rust Target | Speedup |
|----------|---------------|-------------|---------|
| 5km, fast preset | 5s | 0.5s | 10x |
| 10km, standard | 60s | 3s | 20x |
| 10km, detailed | 180s (timeout) | 10s | 18x |
| 20km, detailed | timeout | 30s | ∞ |
| 50km, full | OOM | 120s | ∞ |
### 5.7 Bundle Size Targets
| Component | Python/Electron | Rust/Tauri |
|-----------|----------------|------------|
| Frontend | 10MB | 2MB (Svelte is smaller) |
| Backend executable | 150MB (PyInstaller) | 8MB (Rust binary) |
| Runtime | 10MB (Electron) | 5MB (WebView2 shared) |
| **Total** | **170MB+** | **~15MB** |
---
## Appendix A: Critical Algorithms in Rust
### A.1 SIMD Haversine Distance
```rust
use packed_simd2::f64x4;
use std::f64::consts::PI;
const EARTH_RADIUS: f64 = 6371000.0;
#[inline]
pub fn haversine_batch(
site_lat: f64, site_lon: f64,
lats: &[f64], lons: &[f64],
) -> Vec<f64> {
assert_eq!(lats.len(), lons.len());
let n = lats.len();
let mut distances = vec![0.0; n];
let site_lat_rad = site_lat * PI / 180.0;
let site_lon_rad = site_lon * PI / 180.0;
let cos_site_lat = site_lat_rad.cos();
let sin_site_lat = site_lat_rad.sin();
// Process 4 points at a time with SIMD
let chunks = n / 4;
for i in 0..chunks {
let base = i * 4;
let lat_rad = f64x4::new(
lats[base] * PI / 180.0,
lats[base + 1] * PI / 180.0,
lats[base + 2] * PI / 180.0,
lats[base + 3] * PI / 180.0,
);
let lon_rad = f64x4::new(
lons[base] * PI / 180.0,
lons[base + 1] * PI / 180.0,
lons[base + 2] * PI / 180.0,
lons[base + 3] * PI / 180.0,
);
let dlat = lat_rad - f64x4::splat(site_lat_rad);
let dlon = lon_rad - f64x4::splat(site_lon_rad);
let sin_dlat_2 = (dlat * f64x4::splat(0.5)).sin_cos().0;
let sin_dlon_2 = (dlon * f64x4::splat(0.5)).sin_cos().0;
let a = sin_dlat_2 * sin_dlat_2
+ f64x4::splat(cos_site_lat) * lat_rad.cos()
* sin_dlon_2 * sin_dlon_2;
let c = f64x4::splat(2.0) * a.sqrt().asin();
let dist = f64x4::splat(EARTH_RADIUS) * c;
distances[base..base + 4].copy_from_slice(&dist.to_array());
}
// Handle remainder
for i in chunks * 4..n {
distances[i] = haversine_scalar(site_lat, site_lon, lats[i], lons[i]);
}
distances
}
```
### A.2 Parallel Grid Calculation
```rust
use rayon::prelude::*;
pub fn calculate_coverage_parallel(
sites: &[Site],
settings: &CoverageSettings,
terrain: &TerrainService,
buildings: &BuildingIndex,
progress: impl Fn(f32) + Sync,
) -> Vec<CoveragePoint> {
let grid = generate_grid(sites, settings);
let total = grid.len();
let counter = std::sync::atomic::AtomicUsize::new(0);
grid.par_iter()
.map(|coord| {
let i = counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if i % 1000 == 0 {
progress(i as f32 / total as f32);
}
calculate_point(coord, sites, settings, terrain, buildings)
})
.collect()
}
```
### A.3 R-tree Spatial Index
```rust
use rstar::{RTree, AABB, PointDistance};
pub struct BuildingIndex {
rtree: RTree<BuildingRef>,
}
#[derive(Clone)]
struct BuildingRef {
id: u64,
envelope: AABB<[f64; 2]>,
height: f32,
geometry: Vec<[f64; 2]>,
}
impl rstar::RTreeObject for BuildingRef {
type Envelope = AABB<[f64; 2]>;
fn envelope(&self) -> Self::Envelope { self.envelope }
}
impl BuildingIndex {
pub fn new(buildings: Vec<Building>) -> Self {
let refs: Vec<BuildingRef> = buildings.iter()
.map(|b| BuildingRef {
id: b.id,
envelope: compute_envelope(&b.geometry),
height: b.height,
geometry: b.geometry.clone(),
})
.collect();
Self { rtree: RTree::bulk_load(refs) }
}
pub fn query_line(&self, p1: [f64; 2], p2: [f64; 2]) -> Vec<&BuildingRef> {
let envelope = AABB::from_corners(
[p1[0].min(p2[0]) - 0.001, p1[1].min(p2[1]) - 0.001],
[p1[0].max(p2[0]) + 0.001, p1[1].max(p2[1]) + 0.001],
);
self.rtree
.locate_in_envelope(&envelope)
.filter(|b| line_intersects_polygon(p1, p2, &b.geometry))
.collect()
}
}
```
---
## Appendix B: WebGL Shader Reuse
The GLSL shaders from `WebGLRadialCoverageLayer.tsx` can be used directly in Svelte:
```typescript
// src/lib/webgl/shaders.ts
export const POINT_VERTEX_SHADER = `
attribute vec2 a_position;
attribute vec2 a_pointPos;
attribute float a_pointRsrp;
attribute float a_pointRadius;
varying vec2 v_localPos;
varying float v_rsrp;
void main() {
vec2 pos = a_pointPos + a_position * a_pointRadius;
gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);
v_localPos = a_position;
v_rsrp = a_pointRsrp;
}
`;
export const POINT_FRAGMENT_SHADER = `
precision highp float;
varying vec2 v_localPos;
varying float v_rsrp;
void main() {
float dist = length(v_localPos);
if (dist > 1.0) discard;
float weight = exp(-dist * dist * 2.0);
gl_FragColor = vec4(weight * v_rsrp, weight, 0.0, 1.0);
}
`;
// ... rest of shaders
```
---
## Appendix C: Tauri Configuration
```json
// src-tauri/tauri.conf.json
{
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devPath": "http://localhost:5173",
"distDir": "../build"
},
"package": {
"productName": "RFCP",
"version": "2.0.0"
},
"tauri": {
"bundle": {
"active": true,
"targets": ["nsis", "msi", "app", "dmg", "deb", "appimage"],
"identifier": "one.eliah.rfcp",
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/icon.icns", "icons/icon.ico"],
"resources": ["terrain/**/*"],
"windows": {
"wix": null
}
},
"windows": [
{
"title": "RFCP - RF Coverage Planner",
"width": 1400,
"height": 900,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null
}
}
}
```
---
## Summary
This migration will transform RFCP from a Python/Electron application into a high-performance Rust/Tauri native app. Key benefits:
1. **Performance:** 20-50x faster calculations through Rust + SIMD + Rayon
2. **Size:** 10-15MB bundle vs 170MB+ current
3. **Memory:** Constant memory usage via mmap and streaming
4. **Capability:** 50km+ calculations that currently timeout
The phased approach allows incremental delivery while maintaining functionality throughout the migration.