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

58 KiB
Raw Permalink Blame History

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

  1. Initialize Tauri project

    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

    #[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

    // 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

    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

    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

    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

    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

    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

    // 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

    // 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

    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)

[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:

  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.