58 KiB
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:
# 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)
# 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:
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)
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)
// 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)
// 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)
// 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
// 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
// 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
-
Initialize Tauri project
npm create tauri-app@latest rfcp-tauri -- --template svelte-ts -
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.)
-
Port WebGL layers
- Copy GLSL shaders verbatim
- Adapt TypeScript to Svelte (minimal changes)
- WebGLRadialCoverageLayer → radial-layer.ts
-
Implement Tauri commands as HTTP proxies
#[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?) } -
Port state management
- Zustand → Svelte stores
- Keep same persistence (IndexedDB via idb)
-
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
-
Core geometry in 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 } -
Propagation models
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 -
Dominant path in 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 } } -
Parallel grid processing
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) } } -
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
-
Terrain service
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(()) } } -
OSM client with caching
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 } } -
Spatial indexing
// 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() } } -
Project persistence
- SQLite database for projects
- Binary serialization (MessagePack or bincode)
-
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
-
wgpu compute shaders
// 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); } -
Tiled GPU processing
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, ... } } -
Streaming results
- Emit partial results per tile
- Progressive rendering in UI
-
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)
[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
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
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
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:
// 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
// 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:
- Performance: 20-50x faster calculations through Rust + SIMD + Rayon
- Size: 10-15MB bundle vs 170MB+ current
- Memory: Constant memory usage via mmap and streaming
- Capability: 50km+ calculations that currently timeout
The phased approach allows incremental delivery while maintaining functionality throughout the migration.