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:
The Dust Council 2026-09-04 13:38:47 -07:00
parent e50d43d6e2
commit df080f6571
21 changed files with 2848 additions and 145 deletions

View file

@ -733,3 +733,192 @@ def test_nothing_survives_that_needed_an_impossible_speed():
if 0 < b.at - a.at <= 300 and distance_nm(
a.latitude, a.longitude, b.latitude, b.longitude) > 2:
assert implied_speed_kt(a, b) <= MAX_GROUND_SPEED_KT
# ---------------------------------------------------------------------------
# What is written beside each aircraft
# ---------------------------------------------------------------------------
class _Entry:
"""A register's answer, in the shape flightmap reads it."""
def __init__(self, **over):
self.type_code = over.get("type_code", "B739")
self.model = over.get("model", "737-932ER")
self.registration = over.get("registration", "N904DN")
self.origin_code = over.get("origin_code", "KATL")
self.origin = over.get("origin", "Atlanta")
self.origin_country = over.get("origin_country", "US")
self.destination_code = over.get("destination_code", "EGLL")
self.destination = over.get("destination", "London Heathrow")
self.destination_country = over.get("destination_country", "GB")
# The map draws an airport where a lookup gave it a position.
self.origin_lat = over.get("origin_lat", 33.6367)
self.origin_lon = over.get("origin_lon", -84.4281)
self.destination_lat = over.get("destination_lat", 51.4706)
self.destination_lon = over.get("destination_lon", -0.4619)
def test_the_label_says_height_speed_type_and_both_ends_of_the_route():
track = straight()
rows = fm.label_lines(track, track.fixes[0], "knots", _Entry())
text = [line for line, _flag in rows]
assert text[0].startswith("350") # flight level
assert "480KT" in text[0]
assert "B739 N904DN" in text
assert "KATL" in text and "EGLL" in text
def test_each_end_of_the_route_carries_its_own_country():
track = straight()
rows = dict((line, flag) for line, flag in
fm.label_lines(track, track.fixes[0], "knots", _Entry()))
assert rows["KATL"] == "US"
assert rows["EGLL"] == "GB"
def test_with_no_register_the_label_is_what_the_aircraft_itself_said():
track = straight()
rows = fm.label_lines(track, track.fixes[0], "knots", None)
assert len(rows) == 1 and rows[0][1] == ""
def test_an_airport_with_no_code_is_named_short_rather_than_in_full():
track = straight()
entry = _Entry(origin_code="", origin="Hartsfield Jackson Atlanta "
"International Airport")
said = [line for line, _ in fm.label_lines(track, track.fixes[0],
"knots", entry)]
assert "Hartsfield Jackson" in said
assert not any(len(line) > 20 for line in said), said
def test_the_flag_is_drawn_in_the_flag_colours():
from bandsaunter.flags import COLOUR_ORDER, FLAG_H, FLAG_W
img = np.full((40, 60), fm.BG, dtype=np.uint8)
fm.draw_flag(img, 5, 5, "JP")
patch = img[5:5 + FLAG_H, 5:5 + FLAG_W]
assert (patch >= fm.FLAG).all()
assert (patch < fm.FLAG + len(COLOUR_ORDER)).all()
# White at the corner and red in the middle: the flag of Japan.
assert patch[0, 0] == fm.flag_index("w")
assert patch[4, 6] == fm.flag_index("r")
def test_a_country_with_no_flag_is_named_in_letters_instead():
img = np.full((40, 60), fm.BG, dtype=np.uint8)
fm.draw_flag(img, 5, 5, "ZZ")
assert (img == fm.GRID).any() # the letters, in grey
assert not (img >= fm.FLAG).any() # and no flag pretending to be one
def test_a_flag_off_the_edge_of_the_picture_paints_nothing():
for x, y in ((-40, 5), (5, -40), (200, 5), (5, 200)):
img = np.full((40, 60), fm.BG, dtype=np.uint8)
fm.draw_flag(img, x, y, "US")
assert (img == fm.BG).all(), (x, y)
def test_the_flag_colours_fit_in_the_palette_beside_everything_else():
from bandsaunter.flags import COLOUR_ORDER
assert fm.FLAG + len(COLOUR_ORDER) < fm.TRANSPARENT
assert fm.PALETTE.shape == (256, 3)
# And each index really is the colour it claims.
from bandsaunter.flags import COLOURS
for letter in COLOUR_ORDER:
assert tuple(int(v) for v in fm.PALETTE[fm.flag_index(letter)]) == \
COLOURS[letter]
def test_the_register_is_asked_once_per_aircraft_not_once_per_frame(tmp_path):
"""A five-hundred-frame animation asking the same question five hundred
times would be five hundred times as rude."""
asked = []
class _Book:
def get(self, icao, callsign=""):
asked.append(icao)
return _Entry()
fm.animate(two_aircraft(), tmp_path / "counted.gif", fps=8, seconds=3,
width=400, book=_Book())
assert asked, "the register was never asked at all"
assert len(asked) == len(set(asked)), f"asked twice about the same: {asked}"
def test_the_route_reaches_the_drawn_picture(tmp_path):
"""End to end: a register with a route, and the flags on the frame."""
class _Book:
def get(self, icao, callsign=""):
return _Entry()
tracks = two_aircraft()
view = fm.fit(tracks, width=700)
base = fm.background(view)
known = {t.icao: _Entry() for t in tracks}
frame = fm.render_frame(base, view, tracks, tracks[0].first_seen + 60,
known=known)
assert (frame >= fm.FLAG).any(), "no flag was drawn"
assert _has_text(frame, "KATL")
assert _has_text(frame, "B739")
def test_a_crowded_frame_goes_back_to_the_short_label():
"""Five lines beside each of three hundred aircraft is not more
information, it is a page of overlapping text with a map behind it."""
many = [straight(icao=f"{i:06X}", callsign=f"FLT{i}",
lat=51.0 + i * 0.02, lon=-1.0 + i * 0.02)
for i in range(fm.CROWDED + 4)]
view = fm.fit(many, width=900)
base = fm.background(view)
known = {t.icao: _Entry() for t in many}
frame = fm.render_frame(base, view, many, many[0].first_seen + 60,
known=known)
assert not (frame >= fm.FLAG).any(), "still drawing flags when crowded"
assert not _has_text(frame, "B739")
def test_a_quiet_frame_keeps_every_detail():
tracks = two_aircraft()
view = fm.fit(tracks, width=700)
base = fm.background(view)
known = {t.icao: _Entry() for t in tracks}
frame = fm.render_frame(base, view, tracks, tracks[0].first_seen + 60,
known=known)
assert (frame >= fm.FLAG).any()
assert _has_text(frame, "B739")
def test_the_callsign_and_height_survive_a_crowd():
"""Whatever else goes, what the aircraft itself said stays."""
many = [straight(icao=f"{i:06X}", callsign=f"FLT{i}",
lat=51.0 + i * 0.02, lon=-1.0 + i * 0.02)
for i in range(fm.CROWDED + 4)]
view = fm.fit(many, width=900)
frame = fm.render_frame(fm.background(view), view, many,
many[0].first_seen + 60,
known={t.icao: _Entry() for t in many})
assert _has_text(frame, "FLT0")
def test_labels_step_aside_rather_than_landing_on_each_other():
"""Two aircraft passing close together is exactly the moment somebody is
looking at that part of the picture."""
close = [straight(icao=f"{i:06X}", callsign=f"FLT{i}",
lat=51.0 + i * 0.004, lon=-1.0 + i * 0.004, seconds=60)
for i in range(6)]
view = fm.fit(close, width=800, box=(50.8, -1.4, 51.3, -0.6))
taken = []
base = fm.background(view)
fm.render_frame(base, view, close, close[0].first_seen, labels=True)
# Place them by hand so the boxes can be compared.
for track in close:
now = track.at(track.first_seen)
x, y = view.xy(now.latitude, now.longitude)
fm._label(base, x, y, track, now, fm.RAMP, taken)
for i, one in enumerate(taken):
for two in taken[i + 1:]:
assert not fm._overlaps(one, two), f"{one} overlaps {two}"