A window on the sky, and flags on the routes
The terminal board says what is overhead. This says where: a real map with the aircraft moving on it as the frames arrive, and beside each one a box carrying everything known about the flight -- type and registration, who operates it, where it came from and where it is going, height with a rate of climb, speed and heading, how far away and on what bearing, its position, how many frames it has sent and how long since the last one. Qt is asked for and not required. Four bindings are tried, the module imports on a machine with none of them, and asking for the window without one gets the instructions rather than a traceback -- before the receiver is opened, since nothing is gained by taking the dongle for a window that cannot be drawn. In the menu, "listen now" is now "passive capture" with a realtime display beside it. Closing the window leaves exactly the files pressing control-C leaves, because listen and watch share one read loop and one finishing step; the receiver runs on its own thread, so a slow repaint cannot cost a frame and a slow tile fetch cannot stall the picture. The animation's labels grew to match: flight level and speed, type and registration, and both ends of the route, each with a small flag of the country its airport is in. The flags are a table rather than a network -- twelve pixels by eight, where a flag is the arrangement that makes one recognisable rather than a rendering of the real thing -- and a country not in the table is named by its two letters, since a flag that is nearly another country's is worse than none. Where a route arrives as bare codes the country comes from the ICAO prefix. Four things found on the way. The window ignored --seconds, so "listen for ten minutes" meant something different with a window open; it closes itself now. The register was being asked twice per aircraft, once for labels and once for airport positions. Cached routes had no country in them, so the first real redraw drew no flags at all -- routes are versioned now. And past fourteen aircraft on one frame the labels go back to the callsign, the height and the speed, because five lines beside each of three hundred aircraft is a page of overlapping text with a map somewhere behind it. Long names are folded rather than allowed to stretch a box, breaking at the arrow of a route so the two ends stay whole; and the animation's label placement gained the same ring search the window uses, having only ever tried four spots. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
This commit is contained in:
parent
e50d43d6e2
commit
df080f6571
21 changed files with 2848 additions and 145 deletions
|
|
@ -678,7 +678,12 @@ _AIRCRAFT_INTRO = (
|
|||
"path is 12.5 kHz wide \u2014 so it has its own listening mode here.\n\n"
|
||||
"Everything heard is written to a log as it arrives; the map is drawn "
|
||||
"from that log afterwards, and can be drawn again with different options "
|
||||
"as often as you like."
|
||||
"as often as you like.\n\n"
|
||||
"[bold]Passive capture[/bold] listens and writes, showing a line per "
|
||||
"aircraft in this terminal. [bold]Realtime display[/bold] does the same "
|
||||
"and opens a window with a real map in it, each aircraft moving on it "
|
||||
"with a box beside it saying everything known about the flight. Both "
|
||||
"leave the same files behind."
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -715,17 +720,22 @@ def aircraft_menu(console: Console, cfg: ScanConfig) -> None:
|
|||
options = air.load_options()
|
||||
while True:
|
||||
_rule(console, "aircraft (ADS-B)")
|
||||
console.print(Panel(Text(_AIRCRAFT_INTRO), border_style="blue",
|
||||
padding=(0, 1)))
|
||||
console.print(Panel(Text.from_markup(_AIRCRAFT_INTRO),
|
||||
border_style="blue", padding=(0, 1)))
|
||||
_options_table(console, options, "Listening")
|
||||
console.print()
|
||||
_options_table(console, options, "Drawing")
|
||||
logs = air.logs_in(cfg.output_dir)
|
||||
kept = "no logs yet" if not logs else \
|
||||
f"{len(logs)} log{'s' if len(logs) != 1 else ''}"
|
||||
window = "opens a window" if air.windowed() else \
|
||||
"needs Qt \u2014 see ?"
|
||||
console.print(
|
||||
f"\n [cyan]l[/cyan] [bold green]Listen now[/bold green]"
|
||||
f" [grey62]{air.describe(options)}[/grey62]\n"
|
||||
f"\n [cyan]p[/cyan] [bold green]Passive capture[/bold green]"
|
||||
f" [grey62]{air.describe(options)}[/grey62]\n"
|
||||
f" [cyan]r[/cyan] [bold green]Realtime display[/bold green]"
|
||||
f" [grey62]{window}, the map and the aircraft on it"
|
||||
f"[/grey62]\n"
|
||||
f" [cyan]m[/cyan] Draw a map from a log [grey62]{kept} in "
|
||||
f"{cfg.output_dir}[/grey62]\n"
|
||||
f" [cyan]N[/cyan] change option N "
|
||||
|
|
@ -734,12 +744,15 @@ def aircraft_menu(console: Console, cfg: ScanConfig) -> None:
|
|||
f"[grey62]kept in {air.options_path()}[/grey62]\n"
|
||||
f" [cyan]d[/cyan] Reset them\n"
|
||||
f" [cyan]b[/cyan] Back\n")
|
||||
answer = _ask(console, " choice", "l").strip().lower()
|
||||
answer = _ask(console, " choice", "p").strip().lower()
|
||||
|
||||
if answer in _BACK:
|
||||
return
|
||||
if answer in ("l", "listen"):
|
||||
# "l" was what this was called before there were two of them.
|
||||
if answer in ("p", "l", "passive", "listen"):
|
||||
_listen(console, cfg, options)
|
||||
elif answer in ("r", "realtime", "window"):
|
||||
_watch(console, cfg, options)
|
||||
elif answer in ("m", "map", "draw"):
|
||||
_draw_from_menu(console, cfg, options, logs)
|
||||
elif answer == "s":
|
||||
|
|
@ -756,7 +769,7 @@ def aircraft_menu(console: Console, cfg: ScanConfig) -> None:
|
|||
_edit_option(console, options, answer)
|
||||
else:
|
||||
console.print(" [yellow]enter a number from the lists, or "
|
||||
"l, m, s, d or b[/yellow]")
|
||||
"p, r, m, s, d or b[/yellow]")
|
||||
|
||||
|
||||
def _edit_option(console: Console, options, answer: str) -> None:
|
||||
|
|
@ -825,6 +838,24 @@ def _listen(console: Console, cfg: ScanConfig, options) -> None:
|
|||
"\"Draw when finished\"[/grey62]")
|
||||
|
||||
|
||||
def _watch(console: Console, cfg: ScanConfig, options) -> None:
|
||||
"""Open the window, and come back to the menu when it is closed."""
|
||||
from . import aircraft as air
|
||||
|
||||
errs = options.validate()
|
||||
if errs:
|
||||
for e in errs:
|
||||
console.print(f" [red]{e}[/red]")
|
||||
return
|
||||
console.print("[grey62]closing the window stops the capture and writes "
|
||||
"the log, the report and the map, exactly as the passive "
|
||||
"capture does.[/grey62]")
|
||||
try:
|
||||
air.watch(console, options, cfg.output_dir)
|
||||
except Exception as exc: # a menu must survive it
|
||||
console.print(f" [red]{exc}[/red]")
|
||||
|
||||
|
||||
def _draw_from_menu(console: Console, cfg: ScanConfig, options, logs) -> None:
|
||||
"""Pick a log and draw it, newest first because that is usually the one."""
|
||||
from . import aircraft as air
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue