diff --git a/dump1090.c b/dump1090.c index aa23b73..2df4041 100644 --- a/dump1090.c +++ b/dump1090.c @@ -284,6 +284,10 @@ static void showHelp(void) "--freq Set frequency (default: 1090 Mhz)\n" "--interactive Interactive mode refreshing data on screen. Implies --throttle\n" "--interactive-ttl Remove from list if idle for (default: 60)\n" +"--interactive-show-distance Show aircraft distance and bearing instead of lat/lon\n" +" (requires --lat and --lon)\n" +"--interactive-distance-units Distance units ('km', 'sm', 'nm') (default: 'nm')\n" +"--interactive-callsign-filter Only callsigns that match the prefix or regex will be displayed\n" "--raw Show only messages hex values\n" "--net Enable networking with default ports unless overridden\n" "--modeac Enable decoding of SSR Modes 3/A & 3/C\n" @@ -571,7 +575,20 @@ int main(int argc, char **argv) { Modes.interactive = 1; } else if (!strcmp(argv[j],"--interactive-ttl") && more) { Modes.interactive_display_ttl = (uint64_t)(1000 * atof(argv[++j])); - } else if (!strcmp(argv[j],"--lat") && more) { + } else if (!strcmp(argv[j],"--interactive-show-distance")) { + Modes.interactive_show_distance = 1; + } else if (!strcmp(argv[j], "--interactive-distance-units") && more) { + char *units = argv[++j]; + if (!strcmp(units, "km")) { + Modes.interactive_distance_units = UNIT_KILOMETERS; + } else if (!strcmp(units, "sm")) { + Modes.interactive_distance_units = UNIT_STATUTE_MILES; + } else { + Modes.interactive_distance_units = UNIT_NAUTICAL_MILES; + } + } else if (!strcmp(argv[j], "--interactive-callsign-filter") && more) { + Modes.interactive_callsign_filter = strdup(argv[++j]); + } else if (!strcmp(argv[j], "--lat") && more) { Modes.fUserLat = atof(argv[++j]); } else if (!strcmp(argv[j],"--lon") && more) { Modes.fUserLon = atof(argv[++j]); diff --git a/dump1090.h b/dump1090.h index 088d3f6..3068ea0 100644 --- a/dump1090.h +++ b/dump1090.h @@ -163,6 +163,12 @@ typedef enum { UNIT_METERS } altitude_unit_t; +typedef enum { + UNIT_NAUTICAL_MILES, + UNIT_STATUTE_MILES, + UNIT_KILOMETERS, +} interactive_distance_unit_t; + typedef enum { ALTITUDE_BARO, ALTITUDE_GEOM @@ -336,6 +342,9 @@ struct _Modes { // Internal state uint32_t show_only; // Only show messages from this ICAO int interactive; // Interactive mode uint64_t interactive_display_ttl;// Interactive mode: TTL display + int interactive_show_distance; // Show aircraft distance and bearing instead of lat/lon + interactive_distance_unit_t interactive_distance_units; // Units for interactive distance display + char *interactive_callsign_filter; // Filter for interactive display callsigns uint64_t stats; // Interval (millis) between stats dumps, int stats_range_histo; // Collect/show a range histogram? int onlyaddr; // Print only ICAO addresses diff --git a/interactive.c b/interactive.c index 3e36448..9b55ff3 100644 --- a/interactive.c +++ b/interactive.c @@ -50,6 +50,8 @@ #include "dump1090.h" #include +#include +#include // //========================= Interactive mode =============================== @@ -77,9 +79,40 @@ static int convert_speed(int kts) // Show the currently captured interactive data on screen. // +double distance_units_conversion; +char *distance_units_suffix; +regex_t callsign_filter_regex; + void interactiveInit() { - if (!Modes.interactive) + if (!Modes.interactive) { return; + } + + switch(Modes.interactive_distance_units) { + case UNIT_NAUTICAL_MILES: + distance_units_conversion = 0.53996; + distance_units_suffix = "nm"; + break; + case UNIT_STATUTE_MILES: + distance_units_conversion = 0.621371; + distance_units_suffix = "sm"; + break; + case UNIT_KILOMETERS: + distance_units_conversion = 1.0; + distance_units_suffix = "km"; + break; + } + + if (Modes.interactive_callsign_filter) { + int rc = regcomp(&callsign_filter_regex, Modes.interactive_callsign_filter, + REG_EXTENDED | REG_NOSUB | REG_ICASE); + if (rc != 0) { + char msg[256]; + regerror(rc, &callsign_filter_regex, msg, sizeof(msg)); + fprintf(stderr, "Unable to parse filter '%s': %s\n", Modes.interactive_callsign_filter, msg); + exit(1); + } + } initscr(); clear(); @@ -88,6 +121,7 @@ void interactiveInit() { void interactiveCleanup(void) { if (Modes.interactive) { + regfree(&callsign_filter_regex); endwin(); } } @@ -106,6 +140,11 @@ void interactiveShowData(void) { uint64_t now = mstime(); char progress; char spinner[4] = "|/-\\"; + int valid = 0; + double signalMax = -100.0; + double signalMin = +100.0; + double signalMean = 0.0; + double distanceMax = 0.0; if (!Modes.interactive) return; @@ -116,39 +155,58 @@ void interactiveShowData(void) { next_update = now + MODES_INTERACTIVE_REFRESH_TIME; - mvprintw(0, 0, " Hex Mode Sqwk Flight Alt Spd Hdg Lat Long RSSI Msgs Ti"); - mvhline(1, 0, ACS_HLINE, 80); + if (Modes.interactive_show_distance) { + mvprintw(1, 0, " Hex Mode Sqwk Flight Alt Spd Hdg Dist(%s) Bearing RSSI Msgs Ti", + distance_units_suffix); + } else { + mvprintw(1, 0, " Hex Mode Sqwk Flight Alt Spd Hdg Lat Long RSSI Msgs Ti"); + } + + mvhline(2, 0, ACS_HLINE, 80); progress = spinner[(now/1000)%4]; mvaddch(0, 79, progress); int rows = getmaxy(stdscr); - int row = 2; + int row = 3; + int rowMaxd = 0; + int rowMaxRSSI = 0; + int rowMinRSSI = 0; - while (a && row < rows) { - if (a->reliable && (now - a->seen) < Modes.interactive_display_ttl) { + while (a) { + + if (a->reliable && (now - a->seen) < Modes.interactive_display_ttl + && (Modes.interactive_callsign_filter == NULL || a->callsign_matched + || regexec(&callsign_filter_regex, a->callsign, 0, NULL, 0) == 0)) { char strSquawk[5] = " "; char strFl[7] = " "; char strTt[5] = " "; char strGs[5] = " "; int msgs = a->messages; + a->callsign_matched = 1; + valid++; + if (trackDataValid(&a->squawk_valid)) { - snprintf(strSquawk,5,"%04x", a->squawk); + snprintf(strSquawk, sizeof(strSquawk), "%04x", a->squawk); } if (trackDataValid(&a->gs_valid)) { - snprintf (strGs, 5,"%3d", convert_speed(a->gs)); + snprintf (strGs, sizeof(strGs), "%3d", convert_speed(a->gs)); } if (trackDataValid(&a->track_valid)) { - snprintf (strTt, 5,"%03.0f", a->track); + snprintf (strTt, sizeof(strTt), "%03.0f", a->track); } if (msgs > 99999) { msgs = 99999; } + double distance = 0.0; + char strDistance[8] = " "; + double bearing = 0.0; + char strBearing[9] = " "; char strMode[5] = " "; char strLat[8] = " "; char strLon[9] = " "; @@ -168,28 +226,70 @@ void interactiveShowData(void) { } if (trackDataValid(&a->position_valid)) { - snprintf(strLat, 8,"%7.03f", a->lat); - snprintf(strLon, 9,"%8.03f", a->lon); + snprintf(strLat, sizeof(strLat), "%7.03f", a->lat); + snprintf(strLon, sizeof(strLon), "%8.03f", a->lon); } if (trackDataValid(&a->airground_valid) && a->airground == AG_GROUND) { - snprintf(strFl, 7," grnd"); + snprintf(strFl, sizeof(strFl), "grnd "); } else if (Modes.use_gnss && trackDataValid(&a->altitude_geom_valid)) { - snprintf(strFl, 7, "%5dH", convert_altitude(a->altitude_geom)); + snprintf(strFl, sizeof(strFl), "%5dH", convert_altitude(a->altitude_geom)); } else if (trackDataValid(&a->altitude_baro_valid)) { - snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude_baro)); + snprintf(strFl, sizeof(strFl), "%5d ", convert_altitude(a->altitude_baro)); + } + double signalDisplay = 10.0 * log10(signalAverage); + + + if ((Modes.bUserFlags & MODES_USER_LATLON_VALID) && trackDataValid(&a->position_valid)) { + distance = greatcircle(Modes.fUserLat, Modes.fUserLon, + a->lat, a->lon); + + distance /= 1000.0; + + distance *= distance_units_conversion; + if (distance > distanceMax) { + distanceMax = distance; + } + snprintf(strDistance, sizeof(strDistance), "%5.1f ", distance); + bearing = get_bearing(Modes.fUserLat, Modes.fUserLon, a->lat, a->lon); + snprintf(strBearing, sizeof(strBearing), "%5.0f ", bearing); } - mvprintw(row, 0, "%s%06X %-4s %-4s %-8s %6s %3s %3s %7s %8s %5.1f %5d %2.0f", + if (signalDisplay > signalMax) { + signalMax = signalDisplay; + } + if (signalDisplay < signalMin) { + signalMin = signalDisplay; + } + signalMean += signalDisplay; + + if (row < rows) { + mvprintw(row, 0, "%s%06X %-4s %-4s %-8s %6s %3s %3s %7s %8s %5.1f %5d %2.0f", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : " ", (a->addr & 0xffffff), strMode, strSquawk, a->callsign, strFl, strGs, strTt, - strLat, strLon, 10 * log10(signalAverage), msgs, (now - a->seen)/1000.0); - ++row; + Modes.interactive_show_distance ? strDistance : strLat, + Modes.interactive_show_distance ? strBearing : strLon, + signalDisplay, msgs, (now - a->seen)/1000.0); + + if (signalDisplay >= signalMax) { + rowMaxRSSI = row; + } + if (signalDisplay <= signalMin) { + rowMinRSSI = row; + } + if (distance >= distanceMax) { + rowMaxd = row; + } + + ++row; + } + } a = a->next; } - if (Modes.mode_ac) { + + if (Modes.mode_ac && !Modes.interactive_callsign_filter) { for (unsigned i = 1; i < 4096 && row < rows; ++i) { if (modeAC_match[i] || modeAC_count[i] < 50 || modeAC_age[i] > 5) continue; @@ -200,8 +300,9 @@ void interactiveShowData(void) { int modeC = modeAToModeC(modeA); if (modeC != INVALID_ALTITUDE) { strMode[3] = 'C'; - snprintf(strFl, 7, "%5d ", convert_altitude(modeC * 100)); + snprintf(strFl, sizeof(strFl), "%5d ", convert_altitude(modeC * 100)); } + valid++; mvprintw(row, 0, "%7s %-4s %04x %-8s %6s %3s %3s %7s %8s %5s %5d %2d\n", @@ -221,8 +322,24 @@ void interactiveShowData(void) { } } - move(row, 0); - clrtobot(); + if (rowMaxd > 3 && Modes.interactive_show_distance) { + mvprintw(rowMaxd, 52, "^"); + } + + mvprintw(rowMaxd, 52, "+"); + mvprintw(rowMaxRSSI, 68, "+"); + mvprintw(rowMinRSSI, 68, "-"); + + mvprintw(0, 0, " Tot: %3d Vis: %3d RSSI: Max %5.1f+ Mean %5.1f Min %5.1f- MaxD: %6.1f%s+ ", + valid, (rows-3) < valid ? (rows-3) : valid, signalMax, signalMean / valid, signalMin, distanceMax, + distance_units_suffix); + + if (row < rows) { + move(row, 0); + clrtobot(); + } + move(0, 0); + refresh(); } diff --git a/track.c b/track.c index d18b11d..b5419b8 100644 --- a/track.c +++ b/track.c @@ -207,7 +207,7 @@ static int compare_validity(const data_validity *lhs, const data_validity *rhs) // Distance between points on a spherical earth. // This has up to 0.5% error because the earth isn't actually spherical // (but we don't use it in situations where that matters) -static double greatcircle(double lat0, double lon0, double lat1, double lon1) +double greatcircle(double lat0, double lon0, double lat1, double lon1) { double dlat, dlon; @@ -229,6 +229,25 @@ static double greatcircle(double lat0, double lon0, double lat1, double lon1) return 6371e3 * acos(sin(lat0) * sin(lat1) + cos(lat0) * cos(lat1) * cos(dlon)); } +double get_bearing(double lat0, double lon0, double lat1, double lon1) +{ + double dlon; + + lat0 = lat0 * M_PI / 180.0; + lon0 = lon0 * M_PI / 180.0; + lat1 = lat1 * M_PI / 180.0; + lon1 = lon1 * M_PI / 180.0; + + dlon = (lon1 - lon0); + + double x = (cos(lat0) * sin(lat1)) - + (sin(lat0) * cos(lat1) * cos(dlon)); + double y = sin(dlon) * cos(lat1); + double degree = atan2(y, x) * 180 / M_PI; + + return (degree >= 0)? degree : (degree + 360); +} + static void update_range_histogram(double lat, double lon) { if (Modes.stats_range_histo && (Modes.bUserFlags & MODES_USER_LATLON_VALID)) { @@ -1128,6 +1147,11 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) } if (mm->callsign_valid && accept_data(&a->callsign_valid, mm->source)) { + if (strcmp(a->callsign, mm->callsign) != 0) { + // The callsign changed so tell interactive to + // re-evaluate its callsign filter regex if it has one + a->callsign_matched = 0; + } memcpy(a->callsign, mm->callsign, sizeof(a->callsign)); } diff --git a/track.h b/track.h index dc4f851..ade1267 100644 --- a/track.h +++ b/track.h @@ -108,6 +108,7 @@ struct aircraft { data_validity callsign_valid; char callsign[9]; // Flight number + int callsign_matched; // Interactive callsign filter matched data_validity altitude_baro_valid; int altitude_baro; // Altitude (Baro) @@ -321,4 +322,11 @@ static inline unsigned indexToModeA(unsigned index) return (index & 0007) | ((index & 0070) << 1) | ((index & 0700) << 2) | ((index & 07000) << 3); } +/* Great Circle distance in m */ +double greatcircle(double lat0, double lon0, double lat1, double lon1); + +/* Get bearing from 2 points */ +double get_bearing(double lat0, double lon0, double lat1, double lon1); + + #endif diff --git a/view1090.c b/view1090.c index c27a47f..1912590 100644 --- a/view1090.c +++ b/view1090.c @@ -107,11 +107,15 @@ static void view1090Init(void) { // static void showHelp(void) { printf( -"-----------------------------------------------------------------------------\n" +"-------------------------------------------------------------------------------------\n" "| view1090 ModeS Viewer %45s |\n" -"-----------------------------------------------------------------------------\n" +"-------------------------------------------------------------------------------------\n" "--no-interactive Disable interactive mode, print messages to stdout\n" "--interactive-ttl Remove from list if idle for (default: 60)\n" + "--interactive-show-distance Show aircraft distance and bearing instead of lat/lon\n" + " (requires --lat and --lon)\n" + "--interactive-distance-units Distance units ('km', 'sm', 'nm') (default: 'nm')\n" + "--interactive-callsign-filter Only callsigns that match the prefix or regex will be displayed\n" "--modeac Enable decoding of SSR modes 3/A & 3/C\n" "--net-bo-ipaddr TCP Beast output listen IPv4 (default: 127.0.0.1)\n" "--net-bo-port TCP Beast output listen port (default: 30005)\n" @@ -168,7 +172,20 @@ int main(int argc, char **argv) { Modes.interactive = 0; } else if (!strcmp(argv[j],"--interactive-ttl") && more) { Modes.interactive_display_ttl = (uint64_t)(1000 * atof(argv[++j])); - } else if (!strcmp(argv[j],"--lat") && more) { + } else if (!strcmp(argv[j], "--interactive-show-distance")) { + Modes.interactive_show_distance = 1; + } else if (!strcmp(argv[j], "--interactive-distance-units") && more) { + char *units = argv[++j]; + if (!strcmp(units, "km")) { + Modes.interactive_distance_units = UNIT_KILOMETERS; + } else if (!strcmp(units, "sm")) { + Modes.interactive_distance_units = UNIT_STATUTE_MILES; + } else { + Modes.interactive_distance_units = UNIT_NAUTICAL_MILES; + } + } else if (!strcmp(argv[j], "--interactive-callsign-filter") && more) { + Modes.interactive_callsign_filter = strdup(argv[++j]); + } else if (!strcmp(argv[j], "--lat") && more) { Modes.fUserLat = atof(argv[++j]); } else if (!strcmp(argv[j],"--lon") && more) { Modes.fUserLon = atof(argv[++j]);