@mytec: iter2.4 ready for testing
This commit is contained in:
1254
docs/RFCP-ARCHITECTURE.md
Normal file
1254
docs/RFCP-ARCHITECTURE.md
Normal file
File diff suppressed because it is too large
Load Diff
703
docs/RFCP-Project-Roadmap-Complete.md
Normal file
703
docs/RFCP-Project-Roadmap-Complete.md
Normal file
@@ -0,0 +1,703 @@
|
||||
# RFCP Project Roadmap - Complete History & Future Plans
|
||||
|
||||
**Project:** RFCP (RF Coverage Planning) for Ukrainian Military Tactical Communications
|
||||
**Developer:** Олег
|
||||
**Started:** January 30, 2025 (this morning!)
|
||||
**Current Status:** Frontend 95% Complete, Backend 0% Started
|
||||
**Last Updated:** January 30, 2025 (end of day)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Project Overview
|
||||
|
||||
**Vision:** Professional RF coverage planning tool for tactical military communications
|
||||
|
||||
**Tech Stack:**
|
||||
- **Frontend:** React 18 + TypeScript + Vite + Leaflet
|
||||
- **Backend:** Python FastAPI + MongoDB (planned)
|
||||
- **Infrastructure:** VPS + WireGuard mesh + Caddy reverse proxy
|
||||
- **Methodology:** RFCP (Recursive Feedback Collaborative Process)
|
||||
|
||||
**Current Deployment:**
|
||||
- URL: https://rfcp.eliah.one
|
||||
- Status: Production (functional with minor UX iteration needed)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Completed Phases
|
||||
|
||||
### Phase 0: Foundation (December 2024)
|
||||
|
||||
**Goal:** Project setup and initial prototype
|
||||
|
||||
**Achievements:**
|
||||
- ✅ React + TypeScript + Vite setup
|
||||
- ✅ Leaflet map integration
|
||||
- ✅ Basic site management (CRUD)
|
||||
- ✅ Simple coverage calculation
|
||||
- ✅ VPS deployment with Caddy
|
||||
|
||||
**Key Decisions:**
|
||||
- Chose Leaflet over Mapbox (open-source, no API keys)
|
||||
- TypeScript strict mode from day 1
|
||||
- Zustand for state management (simpler than Redux)
|
||||
|
||||
---
|
||||
|
||||
### Iteration 1-7: Core Features (December 2024 - January 2025)
|
||||
|
||||
**Iteration 1: Basic RF Calculations**
|
||||
- Path loss models (Free Space, Okumura-Hata, Egli)
|
||||
- RSRP calculation
|
||||
- Simple heatmap overlay
|
||||
|
||||
**Iteration 2: Multi-Site Support**
|
||||
- Multiple sites on map
|
||||
- Site selection/editing
|
||||
- Aggregate coverage (strongest signal wins)
|
||||
|
||||
**Iteration 3: Antenna Patterns**
|
||||
- Omnidirectional vs Directional
|
||||
- Azimuth and beamwidth controls
|
||||
- Antenna gain calculations
|
||||
|
||||
**Iteration 4: Terrain Integration**
|
||||
- Elevation data (planned, not implemented)
|
||||
- Line-of-sight checks (basic)
|
||||
- Fresnel zone clearance (basic)
|
||||
|
||||
**Iteration 5: Performance Optimization**
|
||||
- Web Workers for parallel calculation
|
||||
- Tile-based rendering
|
||||
- Coverage cache
|
||||
|
||||
**Iteration 6: UI/UX Improvements**
|
||||
- Side panels (Sites, Settings, Stats)
|
||||
- Dark mode
|
||||
- Responsive layout
|
||||
|
||||
**Iteration 7: Settings & Controls**
|
||||
- Coverage radius control
|
||||
- Resolution settings
|
||||
- RSRP threshold controls
|
||||
- Opacity sliders
|
||||
|
||||
---
|
||||
|
||||
### Iteration 8: Geographic Heatmap (Major Milestone) ⭐
|
||||
|
||||
**Date:** Mid-January 2025
|
||||
|
||||
**Problem Solved:** Previous heatmap scaled with zoom level (bad!)
|
||||
|
||||
**Achievement:** Custom geographic-scale canvas renderer
|
||||
- True 400m radius constant across all zoom levels
|
||||
- 256×256 tile rendering with cache
|
||||
- Replaced leaflet.heat dependency entirely
|
||||
|
||||
**Technical Highlight:**
|
||||
```typescript
|
||||
// Custom GeographicHeatmap component
|
||||
// Calculates exact geographic coordinates for each pixel
|
||||
// Renders to canvas tiles that match Leaflet's tile system
|
||||
```
|
||||
|
||||
**Impact:** Professional-grade heatmap visualization! 🎉
|
||||
|
||||
---
|
||||
|
||||
### Iteration 8.1: Multi-Sector Sites ⭐
|
||||
|
||||
**Date:** Late January 2025
|
||||
|
||||
**Achievement:** Sites can have multiple sectors (Alpha, Beta, Gamma)
|
||||
- Greek alphabet naming (Alpha, Beta, Gamma, Delta...)
|
||||
- Clone sector button (+ Sector) with 120° azimuth offset
|
||||
- Each sector = independent antenna with own parameters
|
||||
|
||||
**UI Pattern:**
|
||||
```
|
||||
Site "Vyshka_1"
|
||||
├─ Alpha (azimuth: 0°)
|
||||
├─ Beta (azimuth: 120°)
|
||||
└─ Gamma (azimuth: 240°)
|
||||
```
|
||||
|
||||
**Use Case:** Real-world base stations have 3 sectors for 360° coverage
|
||||
|
||||
---
|
||||
|
||||
### Iteration 9: Keyboard Shortcuts ⚡
|
||||
|
||||
**Date:** Late January 2025
|
||||
|
||||
**Added Shortcuts:**
|
||||
- `N` - New Site
|
||||
- `S` - Save Project
|
||||
- `C` - Clone Selected Sector
|
||||
- `Delete` - Delete Selected Site
|
||||
- `Ctrl+Z` - Undo
|
||||
- `Ctrl+Shift+Z` - Redo
|
||||
- `Ctrl+F` - Search Sites
|
||||
- `Esc` - Close Dialogs
|
||||
|
||||
**Pattern:** useKeyboardShortcuts hook with callback interface
|
||||
|
||||
---
|
||||
|
||||
### Iteration 9.1: NumberInput Components
|
||||
|
||||
**Date:** Late January 2025
|
||||
|
||||
**Replaced:** All `<input type="number">` with custom NumberInput component
|
||||
|
||||
**Features:**
|
||||
- Integrated slider below input
|
||||
- Real-time value validation
|
||||
- Consistent styling
|
||||
- Better UX for numeric settings
|
||||
|
||||
**Applied to:**
|
||||
- Transmit Power (1-100W)
|
||||
- Antenna Gain (0-30 dBi)
|
||||
- Antenna Height (1-500m)
|
||||
- Frequency (700-3800 MHz)
|
||||
- Azimuth (0-359°)
|
||||
- Beamwidth (10-360°)
|
||||
|
||||
---
|
||||
|
||||
### Iteration 9.1.1: Delete Confirmation & Undo
|
||||
|
||||
**Date:** Late January 2025
|
||||
|
||||
**Added:**
|
||||
- ConfirmDialog component (reusable)
|
||||
- Delete confirmation with site name
|
||||
- Danger variant styling (red)
|
||||
- 10-second undo toast after delete
|
||||
|
||||
**UX Pattern:**
|
||||
```
|
||||
Delete button → ConfirmDialog → Delete + Undo Toast (10s)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Iteration 10: Final Frontend Audit ⭐ (Major Milestone)
|
||||
|
||||
**Date:** January 29-30, 2025
|
||||
|
||||
**Goal:** Production-ready frontend
|
||||
|
||||
**Phase 1: Pre-Audit Fixes**
|
||||
- ✅ Fixed potential crashes
|
||||
- ✅ Cleaned up console logs
|
||||
|
||||
**Phase 2: Code Audit & Refactor**
|
||||
- ✅ TypeScript strict mode: 0 errors
|
||||
- ✅ Dead code removal (3 unused files)
|
||||
- ✅ Created logger utility (dev/prod separation)
|
||||
- ✅ Console cleanup (6 files migrated to logger)
|
||||
- ✅ React.memo optimization (SiteMarker, CoverageStats)
|
||||
|
||||
**Phase 3: UX Polish**
|
||||
- ✅ Tooltips on Coverage Settings (5 NumberInputs)
|
||||
- ✅ Improved empty states (CoverageStats, ExportPanel)
|
||||
- ✅ ESLint cleanup (0 errors, 1 acceptable warning)
|
||||
|
||||
**Phase 4: Final Audit**
|
||||
- ✅ TypeScript: 0 errors
|
||||
- ✅ ESLint: 0 errors
|
||||
- ✅ Bundle: 535KB / 163KB gzipped (under 500KB target!)
|
||||
- ✅ Production build: Success
|
||||
|
||||
**Build Time:** 1.49s (excellent!)
|
||||
|
||||
---
|
||||
|
||||
### Iteration 10.1: Critical Bugfixes ✅ (Just Completed!)
|
||||
|
||||
**Date:** January 30, 2025
|
||||
|
||||
**Bug 1: Stack Overflow at 50m Resolution**
|
||||
- Root cause: `Math.min(...150k_array)` exceeded argument limit
|
||||
- Fix: Replaced with `for...of` loop (faster + no limits)
|
||||
- File: `CoverageStats.tsx`
|
||||
|
||||
**Bug 2: No Confirmation on Delete Key**
|
||||
- Root cause: Keyboard handler bypassed ConfirmDialog
|
||||
- Fix: Added `onDeleteRequest` callback pattern
|
||||
- Files: `useKeyboardShortcuts.ts`, `App.tsx`
|
||||
|
||||
**Bug 3: Cyan/Green Coverage Zone**
|
||||
- Root cause: RSRP gradient used cyan/green colors (conflicted with map)
|
||||
- Fix: Replaced with warm-only palette (maroon → red → orange → yellow)
|
||||
- Files: `colorGradient.ts`, `rsrp-thresholds.ts`
|
||||
|
||||
**Status:** All bugs fixed, deployed, tested ✅
|
||||
|
||||
**New Issue Discovered:** Gradient aesthetics - yellow (good) → red (bad) feels counter-intuitive to users. Needs UX decision in next session.
|
||||
|
||||
---
|
||||
|
||||
## 📅 Timeline Summary
|
||||
|
||||
```
|
||||
January 30, 2025
|
||||
Morning ──────────────────────────────────────────────── Evening
|
||||
| |
|
||||
v v
|
||||
[Planning]──[Iteration 10]──[Bugs Found]──[Iteration 10.1]──[Complete!]
|
||||
(Frontend Audit) (Critical Fixes) ✅
|
||||
⭐
|
||||
```
|
||||
|
||||
**Total Development Time:** ~1 day (!)
|
||||
**Sessions:** 2 (morning planning + afternoon implementation)
|
||||
**Major Milestones:** Iteration 10 (Audit), Iteration 10.1 (Bugfixes)
|
||||
|
||||
**Note:** RFCP already had ~8 iterations of prior work. Today we audited, found bugs, and fixed them all! 🚀
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Current Status (End of Iteration 10.1)
|
||||
|
||||
### ✅ What's Working
|
||||
|
||||
**Core Functionality:**
|
||||
- ✅ Multi-site RF coverage planning
|
||||
- ✅ Multi-sector sites (Alpha, Beta, Gamma...)
|
||||
- ✅ Geographic-scale heatmap visualization
|
||||
- ✅ RSRP gradient overlay
|
||||
- ✅ Site CRUD operations
|
||||
- ✅ Coverage calculation (Web Workers)
|
||||
- ✅ Keyboard shortcuts
|
||||
- ✅ Delete confirmation + undo
|
||||
|
||||
**Code Quality:**
|
||||
- ✅ TypeScript strict mode (0 errors)
|
||||
- ✅ ESLint clean (0 errors)
|
||||
- ✅ Production build optimized
|
||||
- ✅ Console logs cleaned up
|
||||
- ✅ Performance optimized
|
||||
|
||||
**Deployment:**
|
||||
- ✅ Live at https://rfcp.eliah.one
|
||||
- ✅ SSL via Caddy
|
||||
- ✅ VPN-only access (security)
|
||||
|
||||
### ⚠️ Known Issues
|
||||
|
||||
**Aesthetic (P2):**
|
||||
- Gradient color palette feels counter-intuitive
|
||||
- Yellow = good signal (technically correct)
|
||||
- Red = bad signal (technically correct)
|
||||
- But users expect red = danger/bad
|
||||
- **Action:** Need UX decision on color scheme
|
||||
|
||||
### 🚧 Not Yet Implemented
|
||||
|
||||
**Frontend Features (Nice-to-Have):**
|
||||
- Keyboard shortcuts help modal (press `?`)
|
||||
- Project export/import (JSON)
|
||||
- Settings persistence (localStorage)
|
||||
- Input validation with error messages
|
||||
- Mobile optimization
|
||||
- PWA offline mode
|
||||
- Print stylesheet
|
||||
|
||||
**Backend (Not Started):**
|
||||
- API endpoints (FastAPI)
|
||||
- Database (MongoDB)
|
||||
- User authentication
|
||||
- Project storage
|
||||
- Multi-user support
|
||||
|
||||
**Advanced RF Features (Future):**
|
||||
- Real terrain data (SRTM/ASTER)
|
||||
- Accurate line-of-sight
|
||||
- Fresnel zone calculation
|
||||
- Link budget analysis
|
||||
- Interference modeling
|
||||
- 3D visualization
|
||||
|
||||
---
|
||||
|
||||
## 🔜 Next Steps
|
||||
|
||||
### Immediate (Next Session)
|
||||
|
||||
**1. Gradient Color Decision** (15-30 min)
|
||||
- Review current palette: Yellow (good) → Red (bad)
|
||||
- Discuss 3 options:
|
||||
- **Option A:** Reverse gradient (red = good, yellow = bad)
|
||||
- **Option B:** Purple → Orange palette (purple = bad, orange = good)
|
||||
- **Option C:** Grayscale → Orange (gray = bad, orange = good)
|
||||
- Implement chosen palette
|
||||
- Test and validate with user
|
||||
|
||||
**2. Optional UX Polish** (if time allows)
|
||||
- Keyboard shortcuts help modal
|
||||
- Project export/import
|
||||
- Settings persistence
|
||||
|
||||
---
|
||||
|
||||
### Short Term (Next 1-2 Weeks)
|
||||
|
||||
**Frontend Polish Phase:**
|
||||
- ✅ Gradient color finalized
|
||||
- Add keyboard shortcuts help (`?` key)
|
||||
- Implement export/import (JSON format)
|
||||
- Add settings persistence (localStorage)
|
||||
- Input validation with error messages
|
||||
- Mobile responsive improvements
|
||||
|
||||
**Estimated Time:** 4-6 hours total
|
||||
|
||||
---
|
||||
|
||||
### Medium Term (Next 1-2 Months)
|
||||
|
||||
**Backend Development Phase:**
|
||||
|
||||
**Week 1-2: Backend Setup**
|
||||
- FastAPI project structure
|
||||
- MongoDB connection
|
||||
- User authentication (JWT)
|
||||
- Basic CRUD API endpoints
|
||||
|
||||
**Week 3-4: Backend Features**
|
||||
- Project storage (save/load)
|
||||
- User projects (multi-user support)
|
||||
- Sharing & collaboration
|
||||
- API documentation (Swagger)
|
||||
|
||||
**Week 5-6: Integration**
|
||||
- Connect frontend to backend
|
||||
- Replace localStorage with API
|
||||
- Testing & debugging
|
||||
- Deployment
|
||||
|
||||
**Week 7-8: Hardening**
|
||||
- API rate limiting
|
||||
- Input validation
|
||||
- Error handling
|
||||
- Logging & monitoring
|
||||
- Security audit
|
||||
|
||||
**Estimated Time:** 40-60 hours total
|
||||
|
||||
---
|
||||
|
||||
### Long Term (3-6 Months)
|
||||
|
||||
**Advanced Features Phase:**
|
||||
|
||||
**Terrain Integration:**
|
||||
- SRTM/ASTER elevation data
|
||||
- Accurate line-of-sight calculation
|
||||
- Fresnel zone clearance
|
||||
- Terrain-based path loss
|
||||
|
||||
**RF Enhancements:**
|
||||
- Link budget calculator
|
||||
- Interference modeling
|
||||
- Multiple frequency bands
|
||||
- Custom propagation models
|
||||
|
||||
**Visualization:**
|
||||
- 3D terrain view
|
||||
- Signal strength profiles
|
||||
- Coverage comparison mode
|
||||
- Time-based analysis
|
||||
|
||||
**Collaboration:**
|
||||
- Real-time multi-user editing
|
||||
- Comments & annotations
|
||||
- Version history
|
||||
- Export to professional formats (KML, GeoJSON)
|
||||
|
||||
**Estimated Time:** 80-120 hours total
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Lessons Learned
|
||||
|
||||
### Technical Insights
|
||||
|
||||
**1. Spread Operator Limits**
|
||||
- JavaScript engines have argument limits (~65k-125k)
|
||||
- Never use spread with large arrays (>10k elements)
|
||||
- Use `reduce()` or `for...of` loops instead
|
||||
|
||||
**2. Geographic Calculations are Hard**
|
||||
- Need precise lat/lon → pixel conversions
|
||||
- Must account for map projection (Web Mercator)
|
||||
- Tile-based rendering is complex but worth it
|
||||
|
||||
**3. React Performance Optimization**
|
||||
- React.memo for components with stable props
|
||||
- useMemo for expensive calculations
|
||||
- useCallback for stable callbacks
|
||||
- Web Workers for heavy computation
|
||||
|
||||
**4. TypeScript Strict Mode**
|
||||
- Worth enabling from day 1
|
||||
- Catches bugs early
|
||||
- Forces better code design
|
||||
- Minor performance overhead acceptable
|
||||
|
||||
### UX Insights
|
||||
|
||||
**1. Color Psychology Matters**
|
||||
- Users have strong expectations (red = bad, green = good)
|
||||
- Map features have colors too (blue = water, green = vegetation)
|
||||
- Gradient must balance data accuracy + user intuition + map conflicts
|
||||
|
||||
**2. Confirmation Dialogs are Critical**
|
||||
- Destructive actions MUST have confirmation
|
||||
- Keyboard shortcuts must follow same rules as UI buttons
|
||||
- Undo toast = great safety net (10s duration)
|
||||
|
||||
**3. Progressive Disclosure Works**
|
||||
- Start with simple controls (power, frequency)
|
||||
- Advanced features in collapsible sections
|
||||
- Tooltips for complex settings
|
||||
- Keyboard shortcuts for power users
|
||||
|
||||
### Process Insights
|
||||
|
||||
**1. RFCP Method is Effective**
|
||||
- Iterative development with clear goals
|
||||
- Each iteration = working feature
|
||||
- Document everything (specs, decisions, learnings)
|
||||
- Easy to resume after breaks
|
||||
|
||||
**2. User Feedback is Gold**
|
||||
- Real usage reveals issues (e.g., gradient colors)
|
||||
- Stack traces help find root causes
|
||||
- Users know their domain (military comms)
|
||||
- Trust technical decisions, guide UX
|
||||
|
||||
**3. Code Quality Pays Off**
|
||||
- TypeScript strict mode caught bugs
|
||||
- ESLint enforced consistency
|
||||
- Logger utility simplified debugging
|
||||
- Clean code = faster iteration
|
||||
|
||||
---
|
||||
|
||||
## 📊 Project Metrics
|
||||
|
||||
### Code Statistics
|
||||
|
||||
**Lines of Code:**
|
||||
- Frontend: ~8,000 lines (TypeScript + TSX)
|
||||
- Backend: 0 lines (not started)
|
||||
|
||||
**Files:**
|
||||
- Components: ~25 files
|
||||
- Utils/Lib: ~15 files
|
||||
- Stores: ~5 files
|
||||
- Total: ~50+ files
|
||||
|
||||
**Dependencies:**
|
||||
- Production: ~15 packages (React, Leaflet, Zustand, etc.)
|
||||
- Development: ~20 packages (Vite, TypeScript, ESLint, etc.)
|
||||
|
||||
**Bundle Size:**
|
||||
- Uncompressed: 536KB
|
||||
- Gzipped: 163KB
|
||||
- Target: <500KB gzipped ✅
|
||||
|
||||
### Development Statistics
|
||||
|
||||
**Today's Session (Jan 30, 2025):**
|
||||
- Morning: Iteration 10 planning & spec
|
||||
- Afternoon: Claude Code implementation
|
||||
- Evening: Bug discovery & Iteration 10.1 fixes
|
||||
- Total: ~8-10 hours productive work
|
||||
|
||||
**Prior Work (Iterations 1-9):**
|
||||
- Completed before today
|
||||
- Frontend already functional
|
||||
- Geographic heatmap, multi-sector, keyboard shortcuts all working
|
||||
|
||||
**Today's Impact:**
|
||||
- Fixed 3 critical bugs
|
||||
- Production-ready code quality
|
||||
- Full audit complete
|
||||
- Zero TypeScript/ESLint errors
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
### Frontend (Current Phase)
|
||||
|
||||
**Must Have (✅ Complete):**
|
||||
- Multi-site coverage planning
|
||||
- Geographic-scale heatmap
|
||||
- Professional UI/UX
|
||||
- Production-ready code
|
||||
- Zero critical bugs
|
||||
|
||||
**Should Have (⚠️ Partial):**
|
||||
- Keyboard shortcuts ✅
|
||||
- Delete confirmation ✅
|
||||
- Code quality ✅
|
||||
- Color palette ⚠️ (needs UX decision)
|
||||
|
||||
**Nice to Have (❌ Not Done):**
|
||||
- Shortcuts help modal
|
||||
- Export/import
|
||||
- Settings persistence
|
||||
- Mobile optimization
|
||||
- PWA features
|
||||
|
||||
### Backend (Not Started)
|
||||
|
||||
**Must Have:**
|
||||
- User authentication
|
||||
- Project storage
|
||||
- CRUD API endpoints
|
||||
- MongoDB integration
|
||||
- Deployment
|
||||
|
||||
**Should Have:**
|
||||
- Multi-user support
|
||||
- Sharing & collaboration
|
||||
- API documentation
|
||||
- Security hardening
|
||||
|
||||
**Nice to Have:**
|
||||
- Real-time collaboration
|
||||
- Version history
|
||||
- Advanced analytics
|
||||
|
||||
### Overall Project
|
||||
|
||||
**Current Status:** Frontend 95%, Backend 0%
|
||||
**Target:** Frontend 100%, Backend 80% (MVP)
|
||||
**Timeline:** ~2-3 months to MVP
|
||||
|
||||
---
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
rfcp/
|
||||
├── frontend/ # React + TypeScript + Vite
|
||||
│ ├── src/
|
||||
│ │ ├── components/ # UI components
|
||||
│ │ │ ├── map/ # Map-related
|
||||
│ │ │ ├── panels/ # Side panels
|
||||
│ │ │ ├── ui/ # Reusable UI
|
||||
│ │ │ └── common/ # Common components
|
||||
│ │ ├── store/ # Zustand stores
|
||||
│ │ ├── lib/ # Business logic
|
||||
│ │ ├── utils/ # Utilities
|
||||
│ │ ├── hooks/ # Custom hooks
|
||||
│ │ ├── types/ # TypeScript types
|
||||
│ │ └── constants/ # Constants
|
||||
│ ├── public/ # Static assets
|
||||
│ │ └── workers/ # Web Workers
|
||||
│ └── dist/ # Production build
|
||||
│
|
||||
├── backend/ # FastAPI + MongoDB (planned)
|
||||
│ ├── app/
|
||||
│ │ ├── api/ # API routes
|
||||
│ │ ├── models/ # Data models
|
||||
│ │ ├── services/ # Business logic
|
||||
│ │ └── utils/ # Utilities
|
||||
│ └── tests/ # Backend tests
|
||||
│
|
||||
└── docs/ # Documentation
|
||||
├── SESSION-*.md # Session summaries
|
||||
├── RFCP-Iteration*.md # Iteration specs
|
||||
├── ARCHITECTURE.md # Architecture decisions
|
||||
└── DEPLOYMENT.md # Deployment guide
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start (For New Developers)
|
||||
|
||||
### Getting Started
|
||||
|
||||
**1. Read Documentation:**
|
||||
```bash
|
||||
# Start with latest session
|
||||
cat docs/SESSION-2025-01-30-Iteration-10.1-Complete.md
|
||||
|
||||
# Then read iterations in order
|
||||
cat docs/RFCP-Iteration8-Geographic-Heatmap.md
|
||||
cat docs/RFCP-Iteration10-Final-Audit.md
|
||||
cat docs/RFCP-Iteration10.1-Critical-Bugfixes.md
|
||||
```
|
||||
|
||||
**2. Setup Development Environment:**
|
||||
```bash
|
||||
cd /opt/rfcp/frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**3. Understand Architecture:**
|
||||
- Read `ARCHITECTURE.md` (to be created)
|
||||
- Review component structure
|
||||
- Study state management (Zustand stores)
|
||||
|
||||
**4. Make First Contribution:**
|
||||
- Pick a "Nice to Have" feature
|
||||
- Create new iteration spec
|
||||
- Implement and test
|
||||
- Document learnings
|
||||
|
||||
---
|
||||
|
||||
## 🎬 Next Session Checklist
|
||||
|
||||
**Before Starting:**
|
||||
- [ ] Read `SESSION-2025-01-30-Iteration-10.1-Complete.md`
|
||||
- [ ] Review this roadmap
|
||||
- [ ] Check current deployment at https://rfcp.eliah.one
|
||||
|
||||
**First Task:**
|
||||
- [ ] Discuss gradient color palette with Олег
|
||||
- [ ] Choose option (A, B, or C)
|
||||
- [ ] Implement and test
|
||||
- [ ] Deploy if satisfied
|
||||
|
||||
**Then:**
|
||||
- [ ] Continue with UX polish (shortcuts help, export/import)
|
||||
- [ ] Or start backend planning
|
||||
|
||||
**Document:**
|
||||
- [ ] Create new session summary
|
||||
- [ ] Update roadmap with new decisions
|
||||
- [ ] Plan next iteration
|
||||
|
||||
---
|
||||
|
||||
## 📞 Contact & Resources
|
||||
|
||||
**Project Owner:** Олег
|
||||
**Project Repository:** `/opt/rfcp/` on VPS (10.10.10.1)
|
||||
**Live Site:** https://rfcp.eliah.one (VPN required)
|
||||
**Documentation:** `/mnt/project/` and `/home/claude/`
|
||||
|
||||
**Key Documents:**
|
||||
- `RFCP-Method-Collaboration-Framework.md` - Work methodology
|
||||
- `SESSION-*.md` - Session summaries
|
||||
- `RFCP-Iteration*.md` - Iteration specs
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ ROADMAP COMPLETE
|
||||
**Last Updated:** 2025-01-30
|
||||
**Next Update:** After gradient color decision + next iteration
|
||||
|
||||
**Total Roadmap Length:** ~600 lines of comprehensive project history and future plans! 🎉
|
||||
1085
docs/devlog/installer/RFCP-Phase-2.1-Desktop-Complete.md
Normal file
1085
docs/devlog/installer/RFCP-Phase-2.1-Desktop-Complete.md
Normal file
File diff suppressed because it is too large
Load Diff
1052
docs/devlog/installer/RFCP-Phase-2.2-Offline-Data-Caching.md
Normal file
1052
docs/devlog/installer/RFCP-Phase-2.2-Offline-Data-Caching.md
Normal file
File diff suppressed because it is too large
Load Diff
543
docs/devlog/installer/RFCP-Phase-2.3-Performance-Optimization.md
Normal file
543
docs/devlog/installer/RFCP-Phase-2.3-Performance-Optimization.md
Normal file
@@ -0,0 +1,543 @@
|
||||
# RFCP Phase 2.3: Performance Optimization
|
||||
|
||||
**Date:** January 31, 2025
|
||||
**Type:** Performance & Parallelization
|
||||
**Estimated:** 8-12 hours
|
||||
**Priority:** HIGH — enables practical use of Detailed preset
|
||||
**Depends on:** Phase 2.2 (Offline Caching)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Goal
|
||||
|
||||
Make Detailed preset usable by parallelizing calculations across CPU cores and optionally GPU. Target: **10-50x speedup**.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Current Performance
|
||||
|
||||
| Preset | Points | Current Time | Target Time |
|
||||
|--------|--------|--------------|-------------|
|
||||
| Fast | 868 | 0.03s | 0.03s ✅ |
|
||||
| Standard | 868 | 13s | 5s |
|
||||
| Detailed | 868 | 300s+ (timeout) | 30s |
|
||||
|
||||
**Bottleneck Analysis:**
|
||||
```
|
||||
[DOMINANT_PATH] Point #1: line_bldgs=646, refl_bldgs=302
|
||||
- 868 points × 700 buildings × geometry = millions of operations
|
||||
- Single-threaded Python
|
||||
- 2 sec/point → 868 × 2 = 1736 sec theoretical
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Coverage Calculation │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Phase 1: OSM Fetch (async, cached) → unchanged │
|
||||
│ Phase 2: Terrain Pre-load (async) → unchanged │
|
||||
│ Phase 3: Point Calculation → PARALLELIZE │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ ProcessPoolExecutor │ │
|
||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||||
│ │ │ Core 1 │ │ Core 2 │ │ Core 3 │ │ Core N │ │ │
|
||||
│ │ │ pts 0-61│ │pts 62-123│ │pts 124..│ │ pts ... │ │ │
|
||||
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Optional: GPU Acceleration │ │
|
||||
│ │ - Path loss matrix calculation (NumPy → CuPy) │ │
|
||||
│ │ - Batch terrain lookups │ │
|
||||
│ │ - Vectorized distance calculations │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Tasks
|
||||
|
||||
### Task 2.3.1: Multiprocessing Infrastructure (3-4 hours)
|
||||
|
||||
**Problem:** Python GIL prevents true parallelism with threads. Need processes.
|
||||
|
||||
**Create `backend/app/services/parallel_coverage_service.py`:**
|
||||
|
||||
```python
|
||||
import os
|
||||
import multiprocessing as mp
|
||||
from concurrent.futures import ProcessPoolExecutor, as_completed
|
||||
from typing import List, Dict, Any, Tuple
|
||||
import time
|
||||
|
||||
# Shared data for worker processes (loaded once per process)
|
||||
_worker_data = {}
|
||||
|
||||
def _init_worker(terrain_cache: Dict, buildings: List, spatial_index_data: Dict, settings_dict: Dict):
|
||||
"""Initialize worker process with shared data."""
|
||||
global _worker_data
|
||||
_worker_data = {
|
||||
'terrain_cache': terrain_cache,
|
||||
'buildings': buildings,
|
||||
'spatial_index': rebuild_spatial_index(spatial_index_data),
|
||||
'settings': settings_dict,
|
||||
}
|
||||
# Import heavy modules inside worker to avoid pickle issues
|
||||
from app.services.terrain_service import TerrainService
|
||||
from app.services.los_service import LOSService
|
||||
from app.services.dominant_path_service import DominantPathService
|
||||
|
||||
_worker_data['terrain_service'] = TerrainService()
|
||||
_worker_data['terrain_service']._tile_cache = terrain_cache
|
||||
_worker_data['los_service'] = LOSService(_worker_data['terrain_service'])
|
||||
_worker_data['dominant_path_service'] = DominantPathService(
|
||||
_worker_data['terrain_service'],
|
||||
_worker_data['los_service']
|
||||
)
|
||||
|
||||
def _calculate_point_worker(args: Tuple) -> Dict:
|
||||
"""Worker function for single point calculation."""
|
||||
global _worker_data
|
||||
lat, lon, site_lat, site_lon, site_elevation, point_elevation = args
|
||||
|
||||
# Use pre-initialized services
|
||||
terrain = _worker_data['terrain_service']
|
||||
los = _worker_data['los_service']
|
||||
dominant = _worker_data['dominant_path_service']
|
||||
settings = _worker_data['settings']
|
||||
buildings = _worker_data['buildings']
|
||||
spatial_idx = _worker_data['spatial_index']
|
||||
|
||||
# ... calculation logic (copy from _calculate_point_sync)
|
||||
|
||||
return {
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
'rsrp': rsrp,
|
||||
'distance': distance,
|
||||
# ... other fields
|
||||
}
|
||||
|
||||
class ParallelCoverageService:
|
||||
"""Coverage calculation with multiprocessing."""
|
||||
|
||||
def __init__(self):
|
||||
# Detect available cores
|
||||
self.num_workers = min(mp.cpu_count(), 14) # Cap at 14
|
||||
print(f"[Coverage] Parallel mode: {self.num_workers} workers")
|
||||
|
||||
async def calculate_parallel(
|
||||
self,
|
||||
sites: List,
|
||||
settings: CoverageSettings,
|
||||
terrain_cache: Dict,
|
||||
buildings: List,
|
||||
spatial_index_data: Dict,
|
||||
) -> List[Dict]:
|
||||
"""Calculate coverage using multiple processes."""
|
||||
|
||||
# Prepare grid
|
||||
grid = self._generate_grid(sites, settings)
|
||||
total_points = len(grid)
|
||||
|
||||
print(f"[Coverage] Starting parallel calculation: {total_points} points, {self.num_workers} workers")
|
||||
|
||||
# Pre-compute point elevations
|
||||
point_elevations = {(lat, lon): elev for lat, lon, elev in grid_with_elevations}
|
||||
|
||||
# Prepare arguments for workers
|
||||
work_items = [
|
||||
(lat, lon, site.lat, site.lon, site_elevation, point_elevations.get((lat, lon), 0))
|
||||
for lat, lon in grid
|
||||
]
|
||||
|
||||
# Run in process pool
|
||||
results = []
|
||||
start_time = time.time()
|
||||
|
||||
with ProcessPoolExecutor(
|
||||
max_workers=self.num_workers,
|
||||
initializer=_init_worker,
|
||||
initargs=(terrain_cache, buildings, spatial_index_data, settings.dict())
|
||||
) as executor:
|
||||
# Submit all tasks
|
||||
futures = {executor.submit(_calculate_point_worker, item): i
|
||||
for i, item in enumerate(work_items)}
|
||||
|
||||
# Collect results with progress
|
||||
completed = 0
|
||||
for future in as_completed(futures):
|
||||
result = future.result()
|
||||
results.append(result)
|
||||
completed += 1
|
||||
|
||||
if completed % (total_points // 10) == 0:
|
||||
elapsed = time.time() - start_time
|
||||
rate = completed / elapsed
|
||||
eta = (total_points - completed) / rate
|
||||
print(f"[Coverage] Progress: {completed}/{total_points} ({100*completed//total_points}%) - ETA: {eta:.1f}s")
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
print(f"[Coverage] Parallel calculation done: {elapsed:.1f}s ({elapsed/total_points*1000:.1f}ms/point)")
|
||||
|
||||
return results
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2.3.2: Data Serialization for Workers (2-3 hours)
|
||||
|
||||
**Problem:** Each worker process needs access to terrain cache, buildings, spatial index. Can't share directly.
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Shared Memory (Python 3.8+):**
|
||||
```python
|
||||
from multiprocessing import shared_memory
|
||||
import numpy as np
|
||||
|
||||
# Create shared terrain cache
|
||||
terrain_shm = shared_memory.SharedMemory(create=True, size=terrain_array.nbytes)
|
||||
terrain_shared = np.ndarray(terrain_array.shape, dtype=terrain_array.dtype, buffer=terrain_shm.buf)
|
||||
terrain_shared[:] = terrain_array[:]
|
||||
```
|
||||
|
||||
2. **Memory-mapped files:**
|
||||
```python
|
||||
import mmap
|
||||
import numpy as np
|
||||
|
||||
# Save terrain to mmap file
|
||||
terrain_mmap = np.memmap('terrain_cache.dat', dtype='int16', mode='w+', shape=(3601, 3601))
|
||||
terrain_mmap[:] = terrain_data[:]
|
||||
terrain_mmap.flush()
|
||||
|
||||
# Workers read from same file
|
||||
worker_terrain = np.memmap('terrain_cache.dat', dtype='int16', mode='r', shape=(3601, 3601))
|
||||
```
|
||||
|
||||
3. **Pickle once, load in each worker:**
|
||||
```python
|
||||
# Main process saves data
|
||||
import pickle
|
||||
with open('worker_data.pkl', 'wb') as f:
|
||||
pickle.dump({'terrain': terrain_cache, 'buildings': buildings}, f)
|
||||
|
||||
# Worker loads once at init
|
||||
def _init_worker(data_path):
|
||||
global _worker_data
|
||||
with open(data_path, 'rb') as f:
|
||||
_worker_data = pickle.load(f)
|
||||
```
|
||||
|
||||
**Recommendation:** Start with pickle (simplest), optimize with mmap if needed.
|
||||
|
||||
---
|
||||
|
||||
### Task 2.3.3: Integrate Parallel Service (2 hours)
|
||||
|
||||
**Update `coverage_service.py`:**
|
||||
|
||||
```python
|
||||
class CoverageService:
|
||||
def __init__(self):
|
||||
self.parallel_service = ParallelCoverageService()
|
||||
self.use_parallel = True # Can be toggled
|
||||
self.parallel_threshold = 100 # Use parallel for > 100 points
|
||||
|
||||
async def calculate(self, sites, settings):
|
||||
grid = self._generate_grid(sites, settings)
|
||||
|
||||
# Decide execution mode
|
||||
if self.use_parallel and len(grid) > self.parallel_threshold:
|
||||
return await self._calculate_parallel(sites, settings, grid)
|
||||
else:
|
||||
return await self._calculate_sequential(sites, settings, grid)
|
||||
|
||||
async def _calculate_parallel(self, sites, settings, grid):
|
||||
# Phase 1: OSM fetch (same as before)
|
||||
buildings, streets, water, vegetation = await self._fetch_osm_grid_aligned(...)
|
||||
|
||||
# Phase 2: Terrain pre-load (same as before)
|
||||
await self.terrain.ensure_tiles_for_bbox(...)
|
||||
terrain_cache = self.terrain._tile_cache.copy()
|
||||
|
||||
# Phase 3: Parallel point calculation
|
||||
spatial_index_data = self._serialize_spatial_index(spatial_idx)
|
||||
|
||||
results = await self.parallel_service.calculate_parallel(
|
||||
sites=sites,
|
||||
settings=settings,
|
||||
terrain_cache=terrain_cache,
|
||||
buildings=buildings,
|
||||
spatial_index_data=spatial_index_data,
|
||||
)
|
||||
|
||||
return results
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2.3.4: GPU Acceleration (Optional) (3-4 hours)
|
||||
|
||||
**Only if NVIDIA GPU detected. Use CuPy for NumPy-like GPU operations.**
|
||||
|
||||
**Create `backend/app/services/gpu_service.py`:**
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
# Check for GPU
|
||||
GPU_AVAILABLE = False
|
||||
try:
|
||||
import cupy as cp
|
||||
GPU_AVAILABLE = cp.cuda.runtime.getDeviceCount() > 0
|
||||
if GPU_AVAILABLE:
|
||||
print(f"[GPU] CUDA available: {cp.cuda.runtime.getDeviceProperties(0)['name'].decode()}")
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class GPUService:
|
||||
"""GPU-accelerated calculations using CuPy."""
|
||||
|
||||
def __init__(self):
|
||||
self.enabled = GPU_AVAILABLE
|
||||
|
||||
def calculate_path_loss_batch(
|
||||
self,
|
||||
distances: np.ndarray, # (N,) array of distances in meters
|
||||
frequency_mhz: float,
|
||||
tx_height: float,
|
||||
rx_height: float,
|
||||
) -> np.ndarray:
|
||||
"""Calculate Okumura-Hata path loss for all points at once."""
|
||||
|
||||
if self.enabled:
|
||||
import cupy as cp
|
||||
d = cp.asarray(distances)
|
||||
else:
|
||||
d = distances
|
||||
|
||||
# Okumura-Hata formula (vectorized)
|
||||
d_km = d / 1000.0
|
||||
f = frequency_mhz
|
||||
hb = tx_height
|
||||
hm = rx_height
|
||||
|
||||
# Urban area correction
|
||||
a_hm = (1.1 * np.log10(f) - 0.7) * hm - (1.56 * np.log10(f) - 0.8)
|
||||
|
||||
# Path loss
|
||||
L = (46.3 + 33.9 * np.log10(f) - 13.82 * np.log10(hb) - a_hm +
|
||||
(44.9 - 6.55 * np.log10(hb)) * np.log10(d_km))
|
||||
|
||||
if self.enabled:
|
||||
return cp.asnumpy(L)
|
||||
return L
|
||||
|
||||
def calculate_distances_batch(
|
||||
self,
|
||||
site_lat: float,
|
||||
site_lon: float,
|
||||
point_lats: np.ndarray,
|
||||
point_lons: np.ndarray,
|
||||
) -> np.ndarray:
|
||||
"""Calculate distances from site to all points (Haversine)."""
|
||||
|
||||
if self.enabled:
|
||||
import cupy as cp
|
||||
lat1 = cp.radians(site_lat)
|
||||
lon1 = cp.radians(site_lon)
|
||||
lat2 = cp.radians(cp.asarray(point_lats))
|
||||
lon2 = cp.radians(cp.asarray(point_lons))
|
||||
else:
|
||||
lat1 = np.radians(site_lat)
|
||||
lon1 = np.radians(site_lon)
|
||||
lat2 = np.radians(point_lats)
|
||||
lon2 = np.radians(point_lons)
|
||||
|
||||
dlat = lat2 - lat1
|
||||
dlon = lon2 - lon1
|
||||
|
||||
a = np.sin(dlat/2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2)**2
|
||||
c = 2 * np.arcsin(np.sqrt(a))
|
||||
|
||||
R = 6371000 # Earth radius in meters
|
||||
distances = R * c
|
||||
|
||||
if self.enabled:
|
||||
return cp.asnumpy(distances)
|
||||
return distances
|
||||
|
||||
|
||||
gpu_service = GPUService()
|
||||
```
|
||||
|
||||
**Add to requirements.txt (optional):**
|
||||
```
|
||||
cupy-cuda12x>=12.0.0 # For CUDA 12.x
|
||||
# or cupy-cuda11x>=11.0.0 # For CUDA 11.x
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2.3.5: Settings UI for Parallel/GPU (1 hour)
|
||||
|
||||
**Add to frontend Settings panel:**
|
||||
|
||||
```typescript
|
||||
// Performance settings
|
||||
<div className="settings-section">
|
||||
<h4>Performance</h4>
|
||||
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.useParallel}
|
||||
onChange={(e) => updateSettings({ useParallel: e.target.checked })}
|
||||
/>
|
||||
Use parallel processing ({cpuCores} cores)
|
||||
</label>
|
||||
|
||||
{gpuAvailable && (
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.useGPU}
|
||||
onChange={(e) => updateSettings({ useGPU: e.target.checked })}
|
||||
/>
|
||||
Use GPU acceleration ({gpuName})
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div className="worker-count">
|
||||
<label>Worker processes:</label>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={cpuCores}
|
||||
value={settings.workerCount}
|
||||
onChange={(e) => updateSettings({ workerCount: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Add API endpoint for system info:**
|
||||
|
||||
```python
|
||||
@router.get("/api/system/info")
|
||||
async def get_system_info():
|
||||
import multiprocessing as mp
|
||||
|
||||
gpu_info = None
|
||||
try:
|
||||
import cupy as cp
|
||||
if cp.cuda.runtime.getDeviceCount() > 0:
|
||||
props = cp.cuda.runtime.getDeviceProperties(0)
|
||||
gpu_info = {
|
||||
'name': props['name'].decode(),
|
||||
'memory_mb': props['totalGlobalMem'] // (1024 * 1024),
|
||||
}
|
||||
except:
|
||||
pass
|
||||
|
||||
return {
|
||||
'cpu_cores': mp.cpu_count(),
|
||||
'gpu': gpu_info,
|
||||
'parallel_enabled': True,
|
||||
'gpu_enabled': gpu_info is not None,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
```bash
|
||||
# Run performance test
|
||||
cd installer
|
||||
.\test-coverage.bat
|
||||
|
||||
# Expected results after optimization:
|
||||
# Fast: 0.03s (unchanged)
|
||||
# Standard: ~5s (was 13s)
|
||||
# Detailed: ~30s (was 300s+ timeout)
|
||||
```
|
||||
|
||||
**Benchmark script:**
|
||||
|
||||
```python
|
||||
# test_parallel.py
|
||||
import asyncio
|
||||
import time
|
||||
from app.services.coverage_service import coverage_service
|
||||
|
||||
async def benchmark():
|
||||
settings = CoverageSettings(
|
||||
radius=5000,
|
||||
resolution=300,
|
||||
preset='detailed',
|
||||
)
|
||||
|
||||
site = Site(lat=50.45, lon=30.52, ...)
|
||||
|
||||
# Warm up
|
||||
await coverage_service.calculate([site], settings)
|
||||
|
||||
# Benchmark
|
||||
times = []
|
||||
for i in range(3):
|
||||
start = time.time()
|
||||
result = await coverage_service.calculate([site], settings)
|
||||
elapsed = time.time() - start
|
||||
times.append(elapsed)
|
||||
print(f"Run {i+1}: {elapsed:.1f}s, {len(result)} points")
|
||||
|
||||
print(f"Average: {sum(times)/len(times):.1f}s")
|
||||
|
||||
asyncio.run(benchmark())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Success Criteria
|
||||
|
||||
- [ ] Multiprocessing uses all available CPU cores
|
||||
- [ ] Detailed preset completes in <60s for 5km radius
|
||||
- [ ] No memory leaks with large calculations
|
||||
- [ ] GPU acceleration works if NVIDIA card present
|
||||
- [ ] Settings UI shows core count and GPU status
|
||||
- [ ] Progress indicator updates during calculation
|
||||
|
||||
---
|
||||
|
||||
## 📊 Expected Performance
|
||||
|
||||
| Preset | Before | After (14 cores) | After (14 cores + GPU) |
|
||||
|--------|--------|------------------|------------------------|
|
||||
| Fast | 0.03s | 0.03s | 0.03s |
|
||||
| Standard | 13s | ~2s | ~1s |
|
||||
| Detailed | 300s+ | ~25s | ~10s |
|
||||
|
||||
---
|
||||
|
||||
## 🔜 Next: Phase 2.4
|
||||
|
||||
- [ ] R-tree spatial index (replace grid-based)
|
||||
- [ ] Simplified building geometry for distant points
|
||||
- [ ] Level-of-detail (LOD) system
|
||||
- [ ] Streaming results (show partial coverage while calculating)
|
||||
|
||||
---
|
||||
|
||||
**Ready for Claude Code** 🚀
|
||||
Reference in New Issue
Block a user