Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ debian/.debhelper
debian/dump1090-fa*
debian/debhelper-build-stamp
debian/files

# Runtime generated JSON data files
public_html/data/
555 changes: 555 additions & 0 deletions RANGE_OUTLINE.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ $ dpkg-buildpackage -b --no-sign --build-profiles=custom # build
```


## Range Outline Feature

dump1090-fa includes a range outline visualization that shows your receiver's maximum detection range at each bearing (0-359 degrees). The outline is colored based on the altitude of aircraft at maximum range, creating a gradient visualization of your coverage profile.

To configure the data retention period (default 24 hours):
```
--range-outline-retention <hours>
```

For example, to keep range data for 48 hours: `--range-outline-retention 48`

See [RANGE_OUTLINE.md](RANGE_OUTLINE.md) for complete documentation.

## Building manually

You can probably just run "make" after installing the required dependencies.
Expand Down
80 changes: 80 additions & 0 deletions dump1090.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,52 @@ void receiverPositionChanged(float lat, float lon, float alt)
writeJsonToFile("receiver.json", generateReceiverJson); // location changed
}

// Save range outline data to file for persistence across restarts
static void saveRangeOutline(void)
{
if (!Modes.range_outline_persistence_file)
return;

FILE *f = fopen(Modes.range_outline_persistence_file, "wb");
if (!f) {
log_with_timestamp("Failed to save range outline data to %s: %s",
Modes.range_outline_persistence_file, strerror(errno));
return;
}

// Write a simple binary format: three arrays
fwrite(Modes.range_outline_max, sizeof(Modes.range_outline_max), 1, f);
fwrite(Modes.range_outline_updated, sizeof(Modes.range_outline_updated), 1, f);
fwrite(Modes.range_outline_altitude, sizeof(Modes.range_outline_altitude), 1, f);
fclose(f);
}

// Load range outline data from file
static void loadRangeOutline(void)
{
if (!Modes.range_outline_persistence_file)
return;

FILE *f = fopen(Modes.range_outline_persistence_file, "rb");
if (!f) {
// File doesn't exist yet, that's OK
return;
}

// Read the three arrays
if (fread(Modes.range_outline_max, sizeof(Modes.range_outline_max), 1, f) != 1 ||
fread(Modes.range_outline_updated, sizeof(Modes.range_outline_updated), 1, f) != 1 ||
fread(Modes.range_outline_altitude, sizeof(Modes.range_outline_altitude), 1, f) != 1) {
log_with_timestamp("Failed to load range outline data, file may be corrupted");
memset(Modes.range_outline_max, 0, sizeof(Modes.range_outline_max));
memset(Modes.range_outline_updated, 0, sizeof(Modes.range_outline_updated));
memset(Modes.range_outline_altitude, 0, sizeof(Modes.range_outline_altitude));
} else {
log_with_timestamp("Loaded range outline data from %s", Modes.range_outline_persistence_file);
}

fclose(f);
}

//
// =============================== Initialization ===========================
Expand Down Expand Up @@ -145,6 +191,12 @@ static void modesInitConfig(void) {
Modes.adaptive_range_scan_delay = 300;
Modes.adaptive_range_rescan_delay = 3600;

// Range outline persistence - default to /tmp, will be updated if --write-json is used
Modes.range_outline_persistence_file = strdup("/tmp/range_outline.dat");

// Range outline retention - default to 24 hours (converted to milliseconds)
Modes.range_outline_retention_ms = (uint64_t)RANGE_OUTLINE_DEFAULT_RETENTION_HOURS * 3600 * 1000;

sdrInitConfig();
}
//
Expand Down Expand Up @@ -415,6 +467,8 @@ static void showHelp(void)
"--json-stats-every <t> Write json stats output every t seconds (default 60)\n"
"--json-location-accuracy <n> Accuracy of receiver location in json metadata\n"
" (0=no location, 1=approximate, 2=exact)\n"
"--range-outline-retention <h> Set range outline data retention period in hours\n"
" (default: 24)\n"
"\n"
" Interactive mode\n"
"\n"
Expand Down Expand Up @@ -463,6 +517,7 @@ static void backgroundTasks(void) {
static uint64_t next_stats_update;
static uint64_t next_json_stats_update;
static uint64_t next_json, next_history;
static uint64_t next_range_outline_save;

uint64_t now = mstime();

Expand Down Expand Up @@ -547,6 +602,7 @@ static void backgroundTasks(void) {

if (Modes.json_dir && now >= next_json) {
writeJsonToFile("aircraft.json", generateAircraftJson);
writeJsonToFile("range_outline.json", generateRangeOutlineJson);
next_json = now + Modes.json_interval;
}

Expand All @@ -570,6 +626,16 @@ static void backgroundTasks(void) {

next_history = now + HISTORY_INTERVAL;
}

// Periodically save range outline data (every 1 minute)
if (now >= next_range_outline_save) {
if (next_range_outline_save == 0) {
next_range_outline_save = now + 60000; // 1 minute
} else {
saveRangeOutline();
next_range_outline_save += 60000;
}
}
}

//
Expand Down Expand Up @@ -760,6 +826,11 @@ int main(int argc, char **argv) {
// Ignored
} else if (!strcmp(argv[j], "--write-json") && more) {
Modes.json_dir = strdup(argv[++j]);
// Update range outline persistence file to json directory
free(Modes.range_outline_persistence_file);
char pathbuf[PATH_MAX];
snprintf(pathbuf, PATH_MAX, "%s/range_outline.dat", Modes.json_dir);
Modes.range_outline_persistence_file = strdup(pathbuf);
} else if (!strcmp(argv[j], "--write-json-every") && more) {
Modes.json_interval = (uint64_t)(1000 * atof(argv[++j]));
if (Modes.json_interval < 100) // 0.1s
Expand Down Expand Up @@ -806,6 +877,8 @@ int main(int argc, char **argv) {
Modes.adaptive_range_scan_delay = atoi(argv[++j]);
} else if (!strcmp(argv[j], "--adaptive-range-rescan-delay") && more) {
Modes.adaptive_range_rescan_delay = atoi(argv[++j]);
} else if (!strcmp(argv[j], "--range-outline-retention") && more) {
Modes.range_outline_retention_ms = (uint64_t)(atof(argv[++j]) * 3600 * 1000); // convert hours to milliseconds
} else if (sdrHandleOption(argc, argv, &j)) {
/* handled */
} else {
Expand Down Expand Up @@ -863,10 +936,14 @@ int main(int argc, char **argv) {

adaptive_init();

// Load persisted range outline data
loadRangeOutline();

// write initial json files so they're not missing
writeJsonToFile("receiver.json", generateReceiverJson);
writeJsonToFile("stats.json", generateStatsJson);
writeJsonToFile("aircraft.json", generateAircraftJson);
writeJsonToFile("range_outline.json", generateRangeOutlineJson);

interactiveInit();

Expand Down Expand Up @@ -946,6 +1023,9 @@ int main(int argc, char **argv) {
display_stats(&Modes.stats_alltime);
}

// Save range outline data for persistence
saveRangeOutline();

sdrClose();
fifo_destroy();

Expand Down
11 changes: 11 additions & 0 deletions dump1090.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ typedef enum {
#define HISTORY_SIZE 120
#define HISTORY_INTERVAL 30000

// Range outline configuration
#define RANGE_OUTLINE_DEGREES 360
#define RANGE_OUTLINE_DEFAULT_RETENTION_HOURS 24 // Default retention: 24 hours

#define MODES_NOTUSED(V) ((void) V)

#define MAX_AMPLITUDE 65535.0
Expand Down Expand Up @@ -402,6 +406,13 @@ struct _Modes { // Internal state
int bUserFlags; // Flags relating to the user details
double maxRange; // Absolute maximum decoding range, in *metres*

// Range outline tracking
double range_outline_max[RANGE_OUTLINE_DEGREES]; // Maximum range seen at each bearing (0-359 degrees)
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 for each bearing
char *range_outline_persistence_file; // File to persist range outline data
uint64_t range_outline_retention_ms; // Current retention period in milliseconds (configurable at runtime)

// State tracking
struct aircraft *aircrafts;

Expand Down
64 changes: 64 additions & 0 deletions net_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -1729,6 +1729,70 @@ static const char *hazard_enum_string(hazard_t hazard)
}
}

char *generateRangeOutlineJson(const char *url_path, int *len) {
uint64_t now = mstime();
int buflen = 32768;
char *buf = (char *) malloc(buflen), *p = buf, *end = buf+buflen;

MODES_NOTUSED(url_path);

p = safe_snprintf(p, end,
"{ \"now\" : %.1f,\n"
" \"range_outline\" : [",
now / 1000.0);

// Output array of ranges for each degree (0-359)
for (int i = 0; i < RANGE_OUTLINE_DEGREES; i++) {
if (i > 0)
p = safe_snprintf(p, end, ",");

// Output range in meters (or 0 if no data)
if (Modes.range_outline_updated[i] != 0 &&
(now - Modes.range_outline_updated[i]) <= Modes.range_outline_retention_ms) {
p = safe_snprintf(p, end, "%.0f", Modes.range_outline_max[i]);
} else {
p = safe_snprintf(p, end, "0");
}
}

p = safe_snprintf(p, end, "],\n \"range_outline_timestamps\" : [");

// Output array of timestamps for each degree (0-359)
for (int i = 0; i < RANGE_OUTLINE_DEGREES; i++) {
if (i > 0)
p = safe_snprintf(p, end, ",");

// Output timestamp in seconds (or 0 if no data)
if (Modes.range_outline_updated[i] != 0 &&
(now - Modes.range_outline_updated[i]) <= Modes.range_outline_retention_ms) {
p = safe_snprintf(p, end, "%.1f", Modes.range_outline_updated[i] / 1000.0);
} else {
p = safe_snprintf(p, end, "0");
}
}

p = safe_snprintf(p, end, "],\n \"range_outline_altitudes\" : [");

// Output array of altitudes for each degree (0-359)
for (int i = 0; i < RANGE_OUTLINE_DEGREES; i++) {
if (i > 0)
p = safe_snprintf(p, end, ",");

// Output altitude in feet (or null if no data or invalid altitude)
if (Modes.range_outline_updated[i] != 0 &&
(now - Modes.range_outline_updated[i]) <= Modes.range_outline_retention_ms &&
Modes.range_outline_altitude[i] != INVALID_ALTITUDE) {
p = safe_snprintf(p, end, "%d", Modes.range_outline_altitude[i]);
} else {
p = safe_snprintf(p, end, "null");
}
}

p = safe_snprintf(p, end, "]\n}\n");
*len = p-buf;
return buf;
}

char *generateAircraftJson(const char *url_path, int *len) {
uint64_t now = mstime();
struct aircraft *a;
Expand Down
1 change: 1 addition & 0 deletions net_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ char *generateAircraftJson(const char *url_path, int *len);
char *generateStatsJson(const char *url_path, int *len);
char *generateReceiverJson(const char *url_path, int *len);
char *generateHistoryJson(const char *url_path, int *len);
char *generateRangeOutlineJson(const char *url_path, int *len);
void writeJsonToFile(const char *file, char * (*generator) (const char *,int*));

#endif
4 changes: 4 additions & 0 deletions public_html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@
<div class="settingsCheckbox" id="sitepos_checkbox"></div>
<div class="settingsText">Site Position and Range Rings</div>
</div>
<div class="settingsOptionContainer">
<div class="settingsCheckbox" id="range_outline_checkbox"></div>
<div class="settingsText">Range Outline</div>
</div>
<div class="settingsOptionContainer">
<div class="settingsCheckbox" id="actrail_checkbox"></div>
<div class="settingsText">Selected Aircraft Trail</div>
Expand Down
Loading