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
139
tests/test_flags.py
Normal file
139
tests/test_flags.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
"""The little flags, and where a country comes from.
|
||||
|
||||
A flag at twelve pixels by eight is not a rendering of the real thing, so
|
||||
what is checked here is what it has to get right to be worth drawing: the
|
||||
right shape of arrangement, the right colours, and never a flag that belongs
|
||||
to somebody else.
|
||||
"""
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from bandsaunter import flags
|
||||
|
||||
|
||||
def test_every_flag_is_the_size_it_says_it_is():
|
||||
for code, rows in flags.FLAGS.items():
|
||||
assert len(rows) == flags.FLAG_H, code
|
||||
assert all(len(row) == flags.FLAG_W for row in rows), code
|
||||
|
||||
|
||||
def test_every_flag_uses_colours_that_exist():
|
||||
"""A typo in a flag would otherwise come out as silent white."""
|
||||
used = {letter for rows in flags.FLAGS.values()
|
||||
for row in rows for letter in row}
|
||||
assert used <= set(flags.COLOURS), used - set(flags.COLOURS)
|
||||
|
||||
|
||||
def test_the_colours_are_a_fixed_order():
|
||||
"""An index into the animation's palette has to mean one colour for the
|
||||
life of the file it is written into."""
|
||||
assert flags.COLOUR_ORDER == tuple(flags.COLOURS)
|
||||
assert len(flags.COLOUR_ORDER) == len(set(flags.COLOUR_ORDER))
|
||||
|
||||
|
||||
def test_a_country_with_no_flag_here_gets_none_rather_than_a_wrong_one():
|
||||
assert flags.flag_for("ZZ") is None
|
||||
assert flags.pixels_for("ZZ") is None
|
||||
assert flags.known("ZZ") is False
|
||||
assert flags.known("us") is True # case does not matter
|
||||
|
||||
|
||||
def test_a_flag_comes_back_as_pixels():
|
||||
picture = flags.pixels_for("JP")
|
||||
assert picture.shape == (flags.FLAG_H, flags.FLAG_W, 3)
|
||||
assert picture.dtype == np.uint8
|
||||
# White at the corner, red in the middle: that is the flag of Japan.
|
||||
assert tuple(picture[0, 0]) == flags.COLOURS["w"]
|
||||
assert tuple(picture[4, 6]) == flags.COLOURS["r"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("code,hoist,middle,fly", [
|
||||
("FR", "b", "w", "r"), # blue at the hoist, white, red at the fly
|
||||
("IT", "g", "w", "r"),
|
||||
("IE", "g", "w", "o"),
|
||||
("BE", "k", "y", "r"),
|
||||
])
|
||||
def test_a_vertical_tricolour_runs_left_to_right(code, hoist, middle, fly):
|
||||
picture = flags.pixels_for(code)
|
||||
assert tuple(picture[4, 0]) == flags.COLOURS[hoist]
|
||||
assert tuple(picture[4, 6]) == flags.COLOURS[middle]
|
||||
assert tuple(picture[4, 11]) == flags.COLOURS[fly]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("code,top,middle,bottom", [
|
||||
("DE", "k", "r", "y"), # black over red over gold
|
||||
("NL", "r", "w", "b"),
|
||||
("RU", "w", "b", "r"),
|
||||
("HU", "r", "w", "g"),
|
||||
])
|
||||
def test_a_horizontal_tricolour_runs_top_to_bottom(code, top, middle, bottom):
|
||||
picture = flags.pixels_for(code)
|
||||
assert tuple(picture[0, 6]) == flags.COLOURS[top]
|
||||
assert tuple(picture[4, 6]) == flags.COLOURS[middle]
|
||||
assert tuple(picture[7, 6]) == flags.COLOURS[bottom]
|
||||
|
||||
|
||||
def test_the_two_ways_round_are_not_the_same_flag():
|
||||
"""Ireland and Italy are the same three colours in the same order; the
|
||||
Netherlands and Russia are the same three the other way up."""
|
||||
assert flags.flag_for("IE") != flags.flag_for("IT")
|
||||
assert flags.flag_for("NL") != flags.flag_for("RU")
|
||||
|
||||
|
||||
def test_a_nordic_cross_is_off_towards_the_hoist():
|
||||
"""It is what makes those five flags recognisable at any size."""
|
||||
rows = flags.flag_for("SE")
|
||||
upright = [x for x in range(flags.FLAG_W) if rows[0][x] == "y"]
|
||||
assert upright, "no cross at all"
|
||||
assert max(upright) < flags.FLAG_W // 2 + 2, upright
|
||||
|
||||
|
||||
def test_the_two_countries_most_likely_to_be_confused_are_not():
|
||||
"""The United States and Malaysia really do look alike; they must at
|
||||
least differ here."""
|
||||
assert flags.flag_for("US") != flags.flag_for("MY")
|
||||
|
||||
|
||||
def test_the_flags_a_receiver_actually_needs_are_all_here():
|
||||
for code in ("US", "CA", "MX", "GB", "IE", "FR", "DE", "NL", "ES", "IT",
|
||||
"PT", "CH", "AT", "DK", "NO", "SE", "FI", "PL", "RU", "TR",
|
||||
"GR", "JP", "CN", "KR", "IN", "AU", "NZ", "BR", "AR", "ZA",
|
||||
"AE", "QA", "SA", "IL", "EG", "SG", "TH", "PH"):
|
||||
assert flags.known(code), code
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Which country an airport is in
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.parametrize("code,country", [
|
||||
("KSEA", "US"), ("KATL", "US"), ("CYYZ", "CA"), ("EGLL", "GB"),
|
||||
("EIDW", "IE"), ("LFPG", "FR"), ("EDDF", "DE"), ("EHAM", "NL"),
|
||||
("LEMD", "ES"), ("LIRF", "IT"), ("RJTT", "JP"), ("ZBAA", "CN"),
|
||||
("YSSY", "AU"), ("NZAA", "NZ"), ("SBGR", "BR"), ("OMDB", "AE"),
|
||||
("VIDP", "IN"), ("WSSS", "SG"), ("MMMX", "MX"), ("FAOR", "ZA"),
|
||||
])
|
||||
def test_an_airport_code_says_which_country_it_is_in(code, country):
|
||||
"""Some routes arrive as nothing but a pair of codes, and the code is
|
||||
enough: the first letter or two is a region."""
|
||||
assert flags.country_of_icao(code) == country
|
||||
|
||||
|
||||
def test_the_longer_prefix_wins():
|
||||
"""K is the United States and KE is not a prefix at all, but LE is Spain
|
||||
while L on its own is nothing."""
|
||||
assert flags.country_of_icao("LEMD") == "ES"
|
||||
assert flags.country_of_icao("KMIA") == "US"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("code", ["", "XX", "XXXX", "K", "1234", "KSE", None])
|
||||
def test_something_that_is_not_an_airport_code_says_nothing(code):
|
||||
assert flags.country_of_icao(code) == ""
|
||||
|
||||
|
||||
def test_a_country_with_no_flag_still_has_a_code_to_fall_back_on():
|
||||
"""The point of the fallback: an airport in a country not drawn here is
|
||||
still labelled, just in letters."""
|
||||
country = flags.country_of_icao("FQMA") # Mozambique
|
||||
assert country == "MZ"
|
||||
assert flags.flag_for(country) is None
|
||||
Loading…
Add table
Add a link
Reference in a new issue