The Range Outline feature tracks and visualizes the maximum detection range of the ADS-B receiver at each bearing (0-359 degrees). It displays an altitude-colored outline on the map showing the actual coverage area based on received aircraft positions, providing a real-time view of detection capabilities that adapts to environmental conditions, antenna characteristics, and terrain. Each segment of the outline is colored according to the altitude of the aircraft at maximum range for that bearing, creating a gradient visualization that shows both coverage and altitude information.
The backend continuously tracks aircraft positions and calculates the maximum detection range at each degree bearing from the receiver location:
-
Position Updates (
updatePosition()at line 626):- Each time an aircraft position is decoded,
update_range_outline()is called - Distance and bearing from receiver to aircraft are calculated using great circle formulas
- Each time an aircraft position is decoded,
-
Range Outline Updates (
update_range_outline()starting at line 273):- Accepts aircraft object to access altitude information
- Calculates the great circle distance from receiver to aircraft
- Calculates the bearing from receiver to aircraft (0-359 degrees)
- Rounds bearing to nearest integer degree
- Retrieves altitude (prefers barometric, falls back to geometric)
- Updates maximum range for that bearing if:
- This is the first position at this bearing, OR
- This distance exceeds the previous maximum for this bearing
- Records timestamp and altitude of the update
-
Data Structure (
dump1090.hlines 409-414):double range_outline_max[RANGE_OUTLINE_DEGREES]; // Maximum range at each bearing (meters) uint64_t range_outline_updated[RANGE_OUTLINE_DEGREES]; // Timestamp when each bearing was last updated int range_outline_altitude[RANGE_OUTLINE_DEGREES]; // Altitude (feet) of aircraft at maximum range char *range_outline_persistence_file; // File to persist range outline data uint64_t range_outline_retention_ms; // Current retention period in milliseconds
-
Data Expiration (
expireRangeOutline()at line 1472 intrack.c):- Called every second by
trackPeriodicUpdate() - Iterates through all 360 bearings
- Resets bearings to 0 if timestamp exceeds retention period
- Allows the outline to adapt to changing conditions over time
- Called every second by
Range outline data persists across application restarts:
-
Save Function (
saveRangeOutline()starting at line 105):- Writes binary file with three arrays
- Called every 60 seconds by
backgroundTasks() - Also called at shutdown
- File format:
double[360] ranges | uint64_t[360] timestamps | int[360] altitudes
-
Load Function (
loadRangeOutline()starting at line 125):- Reads binary file at startup
- Restores previous range, timestamp, and altitude data
- Logs success/failure messages
-
Storage Location:
- Default:
/tmp/range_outline.dat - When
--write-json <dir>is used:<dir>/range_outline.dat
- Default:
The backend generates JSON output for the web interface:
-
Function (
generateRangeOutlineJson()starting at line 1732):- Generates JSON with current timestamp, range array, timestamp array, and altitude array
- Applies retention filter: outputs 0/null for bearings outside retention window
- Called by
backgroundTasks()at the JSON update interval
-
JSON Format:
{ "now": 1760394506.0, "range_outline": [104761, 211010, ...360 values in meters...], "range_outline_timestamps": [1760390035.1, 1760389259.8, ...360 values in seconds...], "range_outline_altitudes": [35000, 28000, null, ...360 values in feet or null...] } -
Output File:
data/range_outline.json(written at same interval asaircraft.json)
-
Global Variables (lines 10-13):
var RangeOutlineFeature = null; // OpenLayers feature containing the polygon var RangeOutlineLayer = null; // OpenLayers vector layer for display var ShowRangeOutline = false; // Toggle state var RangeOutlineData = null; // Cached JSON data from server
-
Startup (
initRangeOutline()):- Called from
end_load_history()after map initialization - Reads
ShowRangeOutlinepreference from localStorage - If enabled, fetches data and updates checkbox visual state
- Ensures UI state matches saved preference after browser refresh
- Called from
-
UI Setup (
initialize_map()):- Adds checkbox to settings panel (HTML in
public_html/index.html) - Registers click handler for
toggleRangeOutline() - Synchronizes checkbox appearance with boolean state
- Adds checkbox to settings panel (HTML in
-
Periodic Updates (
fetchData()):- If
ShowRangeOutlineis enabled, callsfetchRangeOutline()each refresh interval - Runs alongside aircraft data updates
- If
-
AJAX Request (
fetchRangeOutline()):- Fetches
data/range_outline.jsonfrom server - 5-second timeout, no caching
- On success: stores data and calls
updateRangeOutline() - On failure: silently ignores (outline is optional feature)
- Fetches
-
Coordinate Conversion (
updateRangeOutline()):- Validates data structure (must have 360 ranges, timestamps, and altitudes)
- Iterates through all 360 bearings
- For each bearing:
- Checks if range > 0 and timestamp > 0 (backend sends 0 for expired data)
- Uses effective range (actual if valid, 1 meter if invalid to create small circle at center)
- Calculates lat/lon using
destinationPoint()Haversine formula - Converts to map projection coordinates
- Stores altitude information for coloring
-
Haversine Calculation (
destinationPoint()):- Takes lat/lon origin, bearing, and distance in meters
- Returns destination lat/lon point
- Earth radius: 6,371,000 meters
- Uses standard spherical trigonometry formulas
-
Multi-Segment Rendering:
- Creates 360 individual LineString segments (one for each degree transition)
- Each segment connects bearing N to bearing N+1 (with wraparound from 359 to 0)
- Always draws complete circle: segments without data stay at 1 meter radius
- Color calculation for each segment:
- If both endpoints have valid altitude: uses midpoint altitude
- If only one endpoint has valid altitude: uses that altitude
- If no altitude data: uses default blue color
- Uses existing
PlaneObject.prototype.getAltitudeColor()function for altitude-to-color mapping - Uses existing
PlaneObject.prototype.hslRepr()function for HSL color conversion - Layer properties:
- Width: 2 pixels
- No fill
- zIndex: 99 (below site circles, above base layers)
- Creates smooth gradient effect as colors transition between adjacent segments
-
Toggle Function (
toggleRangeOutline()):- Inverts
ShowRangeOutlineboolean - Saves state to localStorage for persistence
- When enabling:
- Fetches fresh data from server
- Data fetch callback updates display
- When disabling:
- Hides the layer (
setVisible(false)) - Clears all line segment features from the source
- Hides the layer (
- Inverts
-
State Persistence:
- Uses browser localStorage with key
ShowRangeOutline - Values:
'true'or'false'(string) - Restored on page load by
initRangeOutline()
- Uses browser localStorage with key
The feature adds one new command-line option and uses existing options:
-
Range Outline Retention Period: Use
--range-outline-retention <hours>to control data retention- Default: 24 hours
- Specified in hours (accepts decimal values)
- Example:
--range-outline-retention 48for 48 hours - Example:
--range-outline-retention 0.5for 30 minutes - Controls how long range data is kept before expiring
-
Data Directory: Use
--write-json <directory>to specify where JSON and persistence files are written- Default JSON location: Uses the directory specified by
--write-json - Persistence file: Automatically placed in same directory as
range_outline.dat - If
--write-jsonnot specified, persistence uses/tmp/range_outline.dat
- Default JSON location: Uses the directory specified by
-
JSON Update Interval: Use
--write-json-every <seconds>to control update frequency- Default: 1.0 seconds
- Minimum: 0.1 seconds
- Affects how often
range_outline.jsonis regenerated
The data retention period controls how long range data is kept before expiring.
Default Value: 24 hours (defined in dump1090.h line 278 as RANGE_OUTLINE_DEFAULT_RETENTION_HOURS)
Runtime Configuration: Use the --range-outline-retention <hours> command-line option to override the default at startup.
Retention Period Behavior:
- Data older than the retention period is automatically expired and reset to 0
- Expiration happens every second via
expireRangeOutline()intrack.c - Allows outline to adapt to changing conditions (weather, seasonal foliage, antenna adjustments)
- Stored internally as
Modes.range_outline_retention_ms(converted to milliseconds)
The web interface provides a simple on/off toggle:
-
Location: Settings panel → "Range Outline" checkbox (below "Site Position and Range Rings")
-
Behavior:
- Click to enable: Fetches data and displays polygon on map
- Click to disable: Hides polygon, stops fetching updates
- State persists across browser sessions via localStorage
The outline is automatically colored based on altitude using the same color scale as aircraft markers. Each line segment is colored according to the altitude of the aircraft at maximum range for that bearing.
Altitude Color Scale:
- The colors are determined by
PlaneObject.prototype.getAltitudeColor()inplaneObject.js - Higher altitudes appear in different hues than lower altitudes
- Colors smoothly gradient between adjacent bearings
Line Width: To change the line thickness, edit public_html/script.js in the updateRangeOutline() function around line 2995:
lineFeature.setStyle(new ol.style.Style({
stroke: new ol.style.Stroke({
color: color,
width: 2 // Change this value (in pixels)
})
}));Default Color for No Altitude Data: Segments without altitude information use 'rgba(0, 128, 255, 0.8)' (semi-transparent blue). This can be changed in the same location around line 2992.
-
Backend Initialization (
dump1090.cmain()):modesInitConfig() ↓ Sets default persistence file: /tmp/range_outline.dat ↓ Sets default retention: 24 hours * 3600 * 1000 ms Parse --write-json argument ↓ Updates persistence file path: <json_dir>/range_outline.dat loadRangeOutline() ↓ Reads persisted data from file ↓ Restores range_outline_max[] and range_outline_updated[] writeJsonToFile("range_outline.json", generateRangeOutlineJson) ↓ Writes initial JSON (may be empty or contain loaded data) -
Frontend Initialization (
script.js):initialize() ↓ Sets up UI, hides range_outline_column initially initialize_map() ↓ Shows range_outline_column if SitePosition is configured ↓ Registers checkbox click handler end_load_history() ↓ initRangeOutline() ↓ Reads localStorage['ShowRangeOutline'] ↓ If 'true': sets ShowRangeOutline = true ↓ Calls fetchRangeOutline() ↓ Updates checkbox appearance fetchData() starts periodic refresh loop
-
Aircraft Position Received:
demodulate2400() / demodulate2400AC() [demod_2400.c] ↓ Decodes Mode S message detectModeS() [mode_s.c] ↓ Validates message useModesMessage() [mode_s.c] ↓ Processes valid message decodeModesMessage() [mode_s.c] ↓ Extracts position data trackUpdateFromMessage() [track.c] ↓ Updates aircraft state updatePosition() [track.c] ↓ Validates new position ↓ update_range_outline(aircraft) ↓ greatcircle(receiver, aircraft) → distance in meters ↓ get_bearing(receiver, aircraft) → bearing 0-359° ↓ bearing_idx = round(bearing) % 360 ↓ Get altitude (prefer barometric, fallback to geometric) ↓ if (distance > max[bearing_idx] || no data yet) ↓ range_outline_max[bearing_idx] = distance ↓ range_outline_updated[bearing_idx] = now (ms) ↓ range_outline_altitude[bearing_idx] = altitude (feet) -
Periodic Updates (Every 1 Second):
trackPeriodicUpdate() [track.c] ↓ expireRangeOutline() ↓ for each bearing (0-359): ↓ if (now - updated[bearing]) > retention_ms ↓ range_outline_max[bearing] = 0 ↓ range_outline_updated[bearing] = 0 -
JSON Generation (Every 1 Second by Default):
backgroundTasks() [dump1090.c] ↓ if (now >= next_json) ↓ writeJsonToFile("range_outline.json", generateRangeOutlineJson) ↓ generateRangeOutlineJson() [net_io.c] ↓ for each bearing (0-359): ↓ if updated[bearing] != 0 && (now - updated[bearing]) <= retention_ms ↓ output: range_outline[bearing] = max[bearing] ↓ output: range_outline_timestamps[bearing] = updated[bearing] ↓ output: range_outline_altitudes[bearing] = altitude[bearing] or null ↓ else ↓ output: range_outline[bearing] = 0 ↓ output: range_outline_timestamps[bearing] = 0 ↓ output: range_outline_altitudes[bearing] = null -
Persistence (Every 60 Seconds + Shutdown):
backgroundTasks() [dump1090.c] ↓ if (now >= next_range_outline_save) ↓ saveRangeOutline() ↓ fopen(persistence_file, "wb") ↓ fwrite(range_outline_max[360]) ↓ fwrite(range_outline_updated[360]) ↓ fwrite(range_outline_altitude[360]) ↓ fclose() -
Frontend Display (Every Refresh Interval):
fetchData() [script.js] ↓ if (ShowRangeOutline) ↓ fetchRangeOutline() ↓ $.ajax("data/range_outline.json") ↓ on success: ↓ RangeOutlineData = data ↓ updateRangeOutline() ↓ Build points array for all 360 bearings: ↓ range = data.range_outline[bearing] ↓ timestamp = data.range_outline_timestamps[bearing] ↓ altitude = data.range_outline_altitudes[bearing] ↓ isValid = (range > 0 && timestamp > 0) ↓ effectiveRange = isValid ? range : 1 ↓ point = destinationPoint(receiver, bearing, effectiveRange) ↓ points[bearing] = {coord, altitude, isValid} ↓ Clear existing features from layer ↓ for bearing = 0 to 359: ↓ nextBearing = (bearing + 1) % 360 ↓ Create LineString from points[bearing] to points[nextBearing] ↓ Calculate color based on altitude(s) ↓ Apply style with calculated color ↓ Add feature to layer ↓ RangeOutlineLayer.setVisible(true)
RANGE_OUTLINE.md- This documentation file
-
dump1090.h- Data structure definitions- Lines 276-277: Constants for degrees and default retention
- Lines 409-414: State variables in
struct _Modes(includes altitude array)
-
dump1090.c- Persistence and initialization- Lines 105-149:
saveRangeOutline()andloadRangeOutline()functions - Default configuration in
modesInitConfig() - Periodic save logic in
backgroundTasks()(every 60 seconds) - JSON generation call in
backgroundTasks() - Path update when
--write-jsonparsed - Load persisted data at startup
- Write initial JSON at startup
- Save data at shutdown
- Lines 105-149:
-
track.c- Position tracking and expiration- Lines 273-300:
update_range_outline()function (updated to accept aircraft object and store altitude) - Line 635: Call to
update_range_outline()inupdatePosition() - Lines 1472-1480:
expireRangeOutline()function - Line 1497: Call to
expireRangeOutline()intrackPeriodicUpdate()
- Lines 273-300:
-
net_io.c- JSON generation- Lines 1732-1789:
generateRangeOutlineJson()function (includes altitude array output)
- Lines 1732-1789:
-
net_io.h- Function declaration- Declaration of
generateRangeOutlineJson()
- Declaration of
-
public_html/index.html- UI element- Range outline checkbox in settings panel
-
public_html/script.js- Frontend logic- Lines 10-13: Global variables
- Line 221-224: Fetch call in
fetchData() - Line 759: Initialize on startup in
end_load_history() - Lines 1115-1126: Checkbox setup in
initialize_map() - Line 1148: Show column if site position configured
- Lines 2878-3056: Range outline functions (fetch, update with multi-segment rendering, destinationPoint, toggle, init)
-
data/range_outline.json- Runtime JSON output- Generated by backend every JSON update interval
- Read by frontend for display
-
data/range_outline.dat(or/tmp/range_outline.dat) - Persistence file- Binary format: ranges (double[360]) + timestamps (uint64[360]) + altitudes (int[360])
- Updated every 60 seconds and at shutdown
- Loaded at startup
- Note: If upgrading from a version without altitude support, delete this file to start fresh
- Provides sufficient angular resolution for most use cases
- Balance between detail and memory/performance
- 1-degree precision is fine for typical ADS-B reception ranges (50-400+ km)
- Long enough to capture daily patterns and build complete outline
- Short enough to adapt to changing conditions (weather, foliage, antenna modifications)
- Prevents indefinite growth from one-off long-distance reception events
- Compact: 3 arrays × 360 elements = ~11 KB
- Fast to read/write
- Simple implementation without dependencies
- User preference: outline-only provides clear boundary without obscuring map
- Reduces visual clutter
- Better visibility of aircraft icons inside coverage area
- Provides additional insight into coverage characteristics
- High-altitude aircraft typically have longer ranges
- Color gradients show both coverage and altitude profile at a glance
- Uses existing altitude color scale for consistency with aircraft display
- Allows individual coloring of each degree transition
- Creates smooth gradient effect between different altitudes
- More flexible for future enhancements
- Always shows complete circle (small radius for bearings without data yet)
- More efficient: layer and feature persist in memory
- Faster toggle response
- Maintains geometry when disabled, instant re-show when enabled
- Standard OpenLayers pattern
- Backend applies actual expiration: zeros out in-memory data older than retention period
- JSON generation applies read-time filter: outputs 0 for expired bearings
- Frontend skips rendering points with 0 range values
- This layered approach ensures data consistency and clean visualization
-
Check receiver position is configured:
- Settings panel → "Site Position and Range Rings" must be enabled
- Position must be set via
--latand--loncommand-line options
-
Check data exists:
- Look for
data/range_outline.jsonin your JSON directory - File should contain non-zero values in
range_outlinearray - If all zeros, no aircraft have been received yet or data has expired
- Look for
-
Check browser console for errors:
- Press F12 to open developer tools
- Look for failed AJAX requests or JavaScript errors
- Cause: Data has expired (exceeds retention period with no new aircraft at those bearings)
- Solution: Wait for aircraft to be received again, or increase retention period using
--range-outline-retention <hours>and restart dump1090
- Check browser localStorage is enabled
- Verify
initRangeOutline()is being called on page load
- Check ShowRangeOutline is enabled (checkbox checked)
- Check fetchData() is running (aircraft list updating?)
- Check backend is generating JSON (
ls -l data/range_outline.jsonshows recent timestamp) - Check backend is receiving aircraft (dump1090 console shows messages?)
The range outline displays a color gradient based on the altitude of aircraft at maximum range for each bearing:
- Purple/Magenta: High altitude aircraft (typically 35,000+ feet)
- Blue/Cyan: Medium altitude aircraft (typically 15,000-30,000 feet)
- Green/Yellow: Lower altitude aircraft (below 15,000 feet)
- Smooth Gradients: Colors smoothly transition between adjacent bearings
This provides immediate visual feedback about your coverage profile:
- Areas with high-altitude colors suggest good line-of-sight coverage
- Lower altitude colors may indicate terrain limitations or closer aircraft
- Uniform coloring suggests consistent altitude coverage
- Varied coloring shows diverse altitude profiles across different directions
- Starts as a small circle at receiver location
- Gradually expands outward as aircraft are tracked at each bearing
- Segments with no data yet remain at center (1 meter radius)
- Segments extend to maximum observed range when aircraft are detected
Potential improvements not currently implemented:
-
Multiple Outline Layers:
- Show 24-hour, 7-day, and 30-day outlines simultaneously
- Different rendering styles for each time period
-
Export/Import:
- Download range outline data as GeoJSON
- Import previously saved outlines
-
Statistics Display:
- Total coverage area calculation
- Coverage percentage by direction
- Identify weak coverage areas
- Altitude distribution analysis
-
Altitude Band Filtering:
- Toggle between different altitude bands
- Compare low-altitude vs high-altitude coverage
- Better understanding of ground-level vs. high-altitude capabilities
-
Historical Comparison:
- Overlay previous period's outline to see changes
- Detect antenna degradation or improvements
- Track seasonal variations