A simple web app to help determine the best placement for mesh network nodes. Click here to use it: https://thatsfguy.github.io/Line-Of-Sight-Tool/ Or download the repo and use it on your own machine. Terrain files of the continuous 48 States are included. Others you need to download yourself. Pull requests are welcome.
- Browser-based — all data is processed locally (no server upload required).
- Interactive map — visualize node connections using Leaflet.
- CSV input — easily configure and test multiple nodes.
- Privacy-focused — no analytics or trackers.
- Terrain data loaded directly from GitHub.
The RF Line-of-Sight Tool (910 MHz) is a web-based application for analyzing line-of-sight (LOS) for radio frequency (RF) propagation at 910 MHz, used in conjunction with the Node Manager. The Node Manager allows you to create, edit, and organize nodes (locations with geographic coordinates and attributes) and groups for LOS analysis. A group consists of one Primary Node and one or more Secondary Nodes (marked with the "Include" checkbox). Groups are automatically named after the Primary Node, with no option to customize the group name. The LOS tool calculates visibility from the Primary Node to each Secondary Node in a group, displaying results as "clear" or "obstructed" and visualizing paths on a Leaflet map. Data is stored in your browser's localStorage, so export it as a CSV file regularly to avoid loss when clearing your cache or switching devices.
- Node Manager: https://thatsfguy.github.io/Line-Of-Sight-Tool/nodemgr.html
- RF Line-of-Sight Tool: https://thatsfguy.github.io/Line-Of-Sight-Tool/index.html
- Open the Node Manager: Load the Node Manager page in your browser. You'll see two main sections: Nodes and Groups, each with a table for managing data.
- Backup Reminder: A note at the top of both tools warns that data is stored in localStorage. To export:
- In Node Manager, find the "Download CSV" button (typically near the top or in the Nodes section).
- Click to save your nodes and groups as a CSV file. Import this file later to restore data.
- Map Interaction: Node Manager includes an interactive Leaflet map. Use it to place and edit nodes:
- Click on the map to set Latitude (Lat) and Longitude (Lng) for a new or selected node.
- Click a marker (pin) on the map to edit that node's details.
Nodes represent locations (e.g., antennas) with attributes like position, height, and notes, used for LOS calculations.
- Fill in the node details in the provided form:
- Location Name: Enter a descriptive name (e.g., "Tower A").
- Latitude/Longitude: Type values manually (e.g., 37.7749, -122.4194) or click on the map to auto-fill.
- Height (m): Enter the elevation above ground in meters (e.g., 50 for a 50m tower).
- Notes: Add optional details (e.g., "VHF antenna installed").
- Click Add Node to save. The node appears in the Nodes table, and a marker is added to the map.
- In the Nodes table, click the Edit icon/button in the Actions column (likely a pencil icon).
- Alternatively, click the node's marker on the map.
- Update fields as needed (e.g., drag the marker to change coordinates).
- Click Save to apply changes. The table and map update automatically.
- In the Actions column, click the Delete icon/button (likely a trash can).
- Confirm deletion. The node is removed from the table and any associated groups.
| Column | Description |
|---|---|
| Include | Checkbox to mark the node as a Secondary Node for LOS calculations in a group. |
| Primary | Checkbox to designate the node as the Primary Node for LOS calculations in a group. Only one node per group can be Primary. |
| Location Name | Editable text field for the node's name. |
| Latitude | Numeric field for north-south position (e.g., 37.7749). |
| Longitude | Numeric field for east-west position (e.g., -122.4194). |
| Height (m) | Numeric field for elevation above ground (in meters). |
| Notes | Text area for free-form details. |
| Actions | Buttons for Edit/Delete. |
Groups are used to process LOS plots at 910 MHz. Each group consists of one Primary Node and one or more Secondary Nodes (marked with the "Include" checkbox). Groups are automatically named after the Primary Node, with no option to customize the name.
- In the Nodes table, check the Primary box for the node to serve as the Primary Node (only one per group). The group will be named after this node.
- Check the Include boxes for nodes to include as Secondary Nodes.
- Click Create Group to create the group.
- If a group with the same Primary Node already exists, a popup will prompt you to confirm overwriting the existing group. Click OK to overwrite, or cancel to abort. The new group appears in the Groups table, named after the Primary Node.
- In the Nodes table, select a new Primary node or adjust the Include checkboxes for Secondary Nodes.
- Click Create Group.
- A popup will prompt you to confirm overwriting the existing group associated with the selected Primary Node. Click OK to overwrite, or cancel to abort. If overwritten, the group name updates automatically to match the new Primary Node.
- In the Groups table, click the Delete icon/button in the Actions column.
- Confirm. This removes the group but keeps the nodes intact.
| Column | Description |
|---|---|
| Group Name | Automatically set to the Primary Node's name (not editable). |
| Included Nodes | Displays the Primary Node and Secondary Nodes. |
| Actions | Buttons for Delete. |
In the RF Line-of-Sight Tool (https://thatsfguy.github.io/Line-Of-Sight-Tool/index.html), select a group for analysis using the Data Source option, which offers two radio buttons:
-
CSV File:
- Select the "CSV File" radio button.
- An upload option appears. Click to upload a CSV file created manually or exported from Node Manager (using the "Download CSV" button).
- The tool loads the group data from the CSV for analysis.
-
Group from Node Manager:
- Select the "Group from Node Manager" radio button.
- A picklist appears, listing groups stored in localStorage, each named after its Primary Node.
- Select the desired group from the picklist. No CSV upload is needed.
-
Running the Analysis:
- After selecting a group (via CSV or picklist), ensure it has one Primary Node and at least one Secondary Node.
- Initiate the analysis (e.g., click a "Run" or "Analyze" button).
- The tool will:
- Calculate LOS at 910 MHz from the Primary Node to each Secondary Node.
- Display results as "clear" (visible) or "obstructed" (blocked by terrain or obstacles).
- Visualize the paths on a Leaflet map, with lines indicating clear or obstructed paths.
- Map Usage: In Node Manager, zoom/pan the Leaflet map for precise node placement. Markers may be color-coded (e.g., Primary Node in red).
- Group Setup: Ensure exactly one node is marked as Primary per group and at least one node is marked as Include for LOS calculations. The group name automatically matches the Primary Node.
- Data Persistence: Data saves to localStorage—no login needed. Export CSV from Node Manager after significant changes to avoid data loss.
- LOS Analysis:
- Accurate node heights and coordinates are critical for reliable RF LOS calculations at 910 MHz.
- Obstructions (e.g., hills, buildings) are factored in based on terrain data used by the tool.
- Troubleshooting:
- If the map fails to load, verify your internet connection (uses Leaflet).
- No LOS results? Confirm the group has a Primary Node and at least one Secondary Node, and check that the group was loaded correctly (via CSV or picklist).
- For bulk import: Use the "Import CSV" feature in Node Manager to restore previously exported data.
- Limitations: This is a client-side tool with no cloud sync. Desktop browsers provide the best experience.
For issues or advanced features, check the GitHub repo or page source for updates. Happy RF mapping!
This adds shared/collaborative editing to the Line-of-Sight Tool using a simple PHP backend with JSON file storage.
api.php- The PHP backend that handles data storageshared-storage.js- JavaScript that integrates with the frontend
Upload api.php to your web server (any PHP 7.4+ host will work).
Create a data directory in the same folder:
mkdir data
chmod 755 dataCreate a .htaccess file inside the data directory to prevent direct access:
# data/.htaccess
Deny from all
Add the JavaScript to nodemgr.html. Open the file and add this line near the top, inside the <head> tag, BEFORE any other scripts:
<script src="shared-storage.js"></script>Or if hosting the JS elsewhere, use the full URL:
<script src="https://yourserver.com/path/to/shared-storage.js"></script>That's it! No hardcoded configuration needed.
- Open the Node Manager - you'll see a "Local Only" indicator in the top right
- Click the indicator to open Collaboration Settings
- Enter your API URL (where you hosted
api.php) - Click "Create New Key" to generate a 16-character workspace key
- Click "Connect" to start using the shared workspace
- Share the API URL and key with collaborators
- Changes sync automatically (saves within 1 second, polls for updates every 30 seconds)
Each user configures their own API URL and key through the UI - nothing is hardcoded. Settings are remembered in the browser.
Edit the constants at the top of api.php:
| Setting | Default | Description |
|---|---|---|
MAX_KEYS |
50 | Maximum number of workspaces (JSON files) |
MAX_NODES |
400 | Maximum nodes per workspace |
MAX_GROUPS |
100 | Maximum groups per workspace |
MAX_JSON_SIZE |
512KB | Maximum payload size |
KEY_LENGTH |
16 | Length of generated keys |
ALLOWED_ORIGINS |
* |
CORS origins (set to your domain in production) |
| Method | URL | Description |
|---|---|---|
POST |
?action=newkey |
Create a new workspace, returns the key |
GET |
?key=XXXXXXXXXXXXXXXX |
Get all data for a key |
POST |
?key=XXXXXXXXXXXXXXXX |
Save data for a key (JSON body) |
GET |
?action=status |
Get server status and limits |
- Input validation: All data is validated for type, range, and length
- Sanitization: Strings are HTML-encoded to prevent XSS
- Path traversal prevention: Keys are strictly alphanumeric
- Size limits: Payload size, node count, and file count are all limited
- No SQL: JSON files eliminate SQL injection entirely
- CORS headers: Configurable allowed origins
- File locking: Uses
LOCK_EXto prevent race conditions
"Key not found" error
- The key doesn't exist. Create a new one or check for typos.
"Maximum number of shared workspaces reached"
- Delete old JSON files from the
datadirectory, or increaseMAX_KEYS.
"Too many nodes"
- You've hit the 400 node limit. Increase
MAX_NODESor split into multiple workspaces.
Changes not syncing
- Check browser console for network errors
- Verify
API_URLis correct and accessible - Check that the
datadirectory is writable
CORS errors
- Set
ALLOWED_ORIGINSto your specific domain instead of*
All data is stored as JSON files in the data directory. To backup:
cp -r data/ backup-$(date +%Y%m%d)/Each file is named {key}.json and contains the complete workspace state.
A serverless backend for collaborative node editing, running on Cloudflare Workers with KV storage.
- Free tier: 100,000 requests/day
- Fast: Edge deployment worldwide
- No server to manage: Fully serverless
- KV Storage included: 100,000 reads/day, 1,000 writes/day free
npm install -g wranglerwrangler loginThis opens a browser to authenticate.
cd cloudflare-worker
wrangler kv:namespace create "LOS_DATA"You'll see output like:
🌀 Creating namespace with title "los-collab-api-LOS_DATA"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "LOS_DATA", id = "abc123your-namespace-id" }
Edit wrangler.toml and replace YOUR_KV_NAMESPACE_ID_HERE with the ID from step 3:
[[kv_namespaces]]
binding = "LOS_DATA"
id = "abc123your-namespace-id"wrangler deployYou'll get a URL like: https://los-collab-api.YOUR-SUBDOMAIN.workers.dev
That's your API URL!
Your API is now live. Use it in the Node Manager:
- Open Node Manager
- Click the "Local Only" badge
- Enter your Worker URL:
https://los-collab-api.YOUR-SUBDOMAIN.workers.dev - Click "Create New Key"
- Share the key with collaborators
| Method | URL | Description |
|---|---|---|
POST |
?action=newkey |
Create a new workspace |
GET |
?key=XXXXXXXXXXXXXXXX |
Get data for a key |
POST |
?key=XXXXXXXXXXXXXXXX |
Save data (JSON body) |
GET |
?action=status |
Get server status |
Edit the CONFIG object in worker.js:
const CONFIG = {
MAX_KEYS: 50, // Maximum workspaces
MAX_NODES: 400, // Max nodes per workspace
MAX_GROUPS: 100, // Max groups per workspace
MAX_JSON_SIZE: 512000, // 512KB max payload
KEY_LENGTH: 16, // Key length
};Test locally before deploying:
wrangler devThis starts a local server at http://localhost:8787
- Go to Cloudflare Dashboard → Workers → your worker
- Click "Triggers" → "Custom Domains"
- Add your domain (must be on Cloudflare DNS)
View logs in real-time:
wrangler tailOr check the Cloudflare Dashboard → Workers → your worker → "Logs"
Free tier includes:
- 100,000 requests/day
- 100,000 KV reads/day
- 1,000 KV writes/day
For a small collaboration group, you'll never hit these limits.
"KV namespace not found"
- Make sure you ran
wrangler kv:namespace create "LOS_DATA" - Make sure you updated
wrangler.tomlwith the correct ID
CORS errors
- The worker includes CORS headers for all origins
- If you need to restrict origins, edit
CORS_HEADERSinworker.js
"Too many workspaces"
-
Default limit is 50 workspaces
-
Increase
MAX_KEYSin the config, or delete old workspaces via KV dashboard