Skip to content

Commit 00ef9e0

Browse files
Move docs to docs/, consolidate TODOs, update README with value prop
- Add "Who This Is For" and "Why LoRa Map?" sections to README - Expand features table with terrain, matrix, deadzones, mesh paths, mobile - Update test counts (341 backend + 232 frontend = 573) - Move parameters.md to docs/ - Consolidate TODOS.md, TODO.SHIP.md, TODOS.SHIP.md into docs/TODOS.md - Fix session.flush() before task insert, fix P2P path loss regex Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d6c9ba6 commit 00ef9e0

File tree

7 files changed

+187
-157
lines changed

7 files changed

+187
-157
lines changed

README.md

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,25 @@
2121

2222
---
2323

24+
## Who This Is For
25+
26+
Meshcore and LoRa communities with fixed tower infrastructure who want to **plan new sites with real propagation physics** and **share current coverage maps with their community**. Admins run simulations; visitors see the results on an interactive map — no RF expertise required.
27+
28+
## Why LoRa Map?
29+
30+
The [upstream planner](https://github.com/meshtastic/meshtastic-site-planner) runs single-tower, single-terrain simulations. LoRa Map builds on that foundation with multi-tower network planning, real-world terrain modeling, and a visitor-facing coverage portal.
31+
32+
- **Multi-source terrain** — Go beyond flat bare-earth SRTM. Simulate against Copernicus DSM (buildings + tree canopy), ESA WorldCover LULC clutter, or a weighted aggregate that blends all three for a realistic "expected coverage" estimate.
33+
- **Batch simulation matrix** — Pre-compute coverage for every combination of client hardware, antenna, and terrain model. Visitors instantly switch scenarios — no simulation wait, no admin involvement.
34+
- **Deadzone remediation** — Automatically find coverage gaps across your network, rank them by priority, and suggest where to place new towers with estimated coverage gain.
35+
- **Mesh path analysis** — Pairwise SPLAT! point-to-point between all towers. See line-of-sight status, path loss, and link quality as color-coded polylines — understand your mesh backbone at a glance.
36+
- **Overlap visualization** — Per-tower color-coded layers with signal-strength transparency. Multi-coverage areas render as cross-hatched patterns so you can distinguish each tower's contribution.
37+
- **Hardware-aware presets** — Heltec V3/V4 profiles, region-locked frequencies (CA/US/EU/AU/AS), curated antennas with automatic SWR mismatch loss calculated and deducted.
38+
- **Visitor portal** — Community members pick their client device and antenna from dropdowns and see personalized coverage maps. No login, no configuration, instant results.
39+
- **One container** — Single Podman image, one port, one volume mount. Deploys behind your existing reverse proxy in minutes.
40+
41+
---
42+
2443
## Table of Contents
2544

2645
- [Overview](#-overview)
@@ -51,11 +70,15 @@ Terrain elevation data is streamed from [AWS Open Data](https://registry.opendat
5170
| Category | Details |
5271
|---|---|
5372
| **Coverage Prediction** | ITM/Longley-Rice propagation model via SPLAT!, configurable frequency/power/gain/height, per-tower color-coded layers with signal-strength alpha mapping |
54-
| **Hardware Presets** | Pre-configured profiles for Heltec V3/V4, region-locked frequencies (CA/US/EU/AU/AS), curated antenna list with SWR mismatch loss calculation |
55-
| **Multi-Tower Support** | Independent per-tower layers, visibility toggling without re-rendering, automatic color assignment from a 24-color palette |
56-
| **Admin/Visitor Roles** | Admin credentials gate simulation triggers and tower management; visitors see cached results instantly with no edit capability |
57-
| **Persistent Storage** | Tower configs, simulation results, and GeoTIFF blobs persisted in SQLite on a mounted volume |
58-
| **Single Container** | One Podman container, HTTP on port 8080, sits behind your existing HTTPS reverse proxy |
73+
| **Multi-Source Terrain** | Bare-earth SRTM, Copernicus DSM (buildings + canopy), ESA WorldCover LULC clutter, and weighted aggregate blend mode |
74+
| **Batch Simulation Matrix** | Admin-configurable client hardware × antenna × terrain combinations, pre-computed so visitors get instant layer switching |
75+
| **Deadzone Remediation** | Gap analysis across all towers, priority-scored deadzone regions, suggested new tower placements with estimated coverage gain |
76+
| **Mesh Path Analysis** | Pairwise SPLAT! point-to-point between towers, path loss and LOS status, color-coded polyline overlay |
77+
| **Overlap Visualization** | Cross-hatched canvas layer distinguishes per-tower signal contributions in multi-coverage areas |
78+
| **Hardware Presets** | Heltec V3/V4 profiles, region-locked frequencies (CA/US/EU/AU/AS), curated antennas with SWR mismatch loss |
79+
| **Admin/Visitor Roles** | Rate-limited admin auth gates mutations; visitors see cached results instantly with client hardware/antenna/terrain selectors |
80+
| **Mobile Responsive** | Full responsive layout with touch-friendly controls, sticky simulation button, and adaptive offcanvas sidebar |
81+
| **Single Container** | One Podman container, HTTP on port 8080, SQLite on a mounted volume, sits behind your existing reverse proxy |
5982

6083
---
6184

@@ -149,9 +172,9 @@ pnpm run test
149172

150173
| Suite | Framework | Tests |
151174
|---|---|---|
152-
| Backend | pytest | 196 |
153-
| Frontend | Vitest | 50 |
154-
| **Total** | | **246** |
175+
| Backend | pytest | 341 |
176+
| Frontend | Vitest | 232 |
177+
| **Total** | | **573** |
155178

156179
All tests run against real code with zero mocks — SPLAT! binaries are built from source during test setup.
157180

@@ -162,11 +185,11 @@ All tests run against real code with zero mocks — SPLAT! binaries are built fr
162185
This tool runs a physics simulation with the following key assumptions:
163186

164187
1. **Terrain resolution:** SRTM elevation data is accurate to ~90 m (3-arcsecond) or ~30 m (1-arcsecond HD mode).
165-
2. **No surface clutter by default:** Buildings, trees, and other obstructions beyond terrain are not modeled. The uniform `clutter_height` parameter can approximate ground-level obstructions. Future support for DSM tiles and LULC-burned clutter is planned.
188+
2. **Surface clutter is optional:** By default, bare-earth SRTM terrain has no buildings or vegetation. Enable DSM (Copernicus GLO-30) or LULC clutter (ESA WorldCover) terrain modes for simulations that include surface obstructions. The weighted aggregate mode blends all three sources.
166189
3. **Isotropic antennas:** Horizontal radiation patterns are assumed omnidirectional. Directional antenna patterns are not modeled.
167190
4. **No skywave propagation:** Upper-atmosphere reflections are assumed negligible, which is less accurate below ~50 MHz.
168191

169-
A detailed description of all model parameters and their recommended values is available in [parameters.md](parameters.md).
192+
A detailed description of all model parameters and their recommended values is available in [docs/parameters.md](docs/parameters.md).
170193

171194
---
172195

TODO.SHIP.md

Lines changed: 0 additions & 29 deletions
This file was deleted.

TODOS.SHIP.md

Lines changed: 0 additions & 117 deletions
This file was deleted.

app/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ async def predict(
341341
color: str = payload.color if payload.color else next_tower_color(existing_colors)
342342

343343
session.add(Tower(id=tower_id, name="Unnamed", color=color, params=payload.model_dump()))
344+
session.flush()
344345
session.add(Task(id=task_id, tower_id=tower_id, status="processing"))
345346

346347
config = get_matrix_config(session)

app/services/splat.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,7 @@ def _parse_p2p_output(stdout: str, fallback_distance_km: float, frequency_mhz: f
971971
line_stripped = line.strip()
972972

973973
# Parse path loss — prefer ITM/ITWOM over free-space
974-
loss_match = re.search(r"(?:ITM|ITWOM)[^\d]*path loss:\s*([\d.]+)\s*dB", line_stripped, re.IGNORECASE)
974+
loss_match = re.search(r"(?:ITM|ITWOM).*?path loss:\s*([\d.]+)\s*dB", line_stripped, re.IGNORECASE)
975975
if loss_match:
976976
path_loss_db = float(loss_match.group(1))
977977

0 commit comments

Comments
 (0)