ADS-B map: altitude-colored aircraft icons + click-to-show flight trail
Icons were 16px accent-colored glyphs, indistinguishable from OSM's own
airport symbols. Now a 30px outlined airliner silhouette filled on
tar1090/ADS-B Exchange's altitude hue ramp, with a callsign/altitude
hover tooltip; the selected aircraft grows and gets a white outline.
Clicking an aircraft draws the path heard so far, segment-colored by
altitude. c2-core writes one point per position change to
aircraft/{icao}/positions (deduped in-process, writes now concurrent);
points carry expire_at and a TTL fieldOverride deletes them after ~24h.
Trail reads are gated on the parent aircraft doc's org via get(), so the
query needs no org filter or composite index. The latest stretch without
a 20-min gap counts as the current flight.
Verified: c2-core pytest 479 passed; frontend tsc --noEmit clean (node:20
container on radio-box).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9b83f0ec6d
commit
bff69a1d04
@@ -24,6 +24,11 @@ def _override(decoded: dict):
|
||||
|
||||
def teardown_function():
|
||||
app.dependency_overrides.pop(require_node_service_or_firebase_token, None)
|
||||
telemetry._last_position.clear()
|
||||
|
||||
|
||||
def _writes_to(mock_set, collection_prefix: str):
|
||||
return [c for c in mock_set.await_args_list if c.args[0].startswith(collection_prefix)]
|
||||
|
||||
|
||||
def test_service_token_without_node_id_is_rejected():
|
||||
@@ -41,8 +46,9 @@ def test_node_upload_upserts_and_stamps_org_id():
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {"ok": True, "count": 1}
|
||||
mock_set.assert_awaited_once()
|
||||
(collection, doc_id, doc), kwargs = mock_set.await_args
|
||||
snapshot = [c for c in mock_set.await_args_list if c.args[0] == "aircraft"]
|
||||
assert len(snapshot) == 1
|
||||
(collection, doc_id, doc), kwargs = snapshot[0]
|
||||
assert collection == "aircraft"
|
||||
assert doc_id == "A1B2C3"
|
||||
assert doc["node_id"] == "node-1"
|
||||
@@ -92,3 +98,38 @@ def test_ais_node_upload_skips_entries_missing_mmsi():
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {"ok": True, "count": 0}
|
||||
mock_set.assert_not_awaited()
|
||||
|
||||
|
||||
def _post_adsb(aircraft):
|
||||
with patch.object(telemetry.fstore, "doc_get_cached", AsyncMock(return_value={"org_id": "org-A"})), \
|
||||
patch.object(telemetry.fstore, "doc_set", AsyncMock()) as mock_set:
|
||||
resp = client.post("/telemetry/adsb", json={"aircraft": aircraft})
|
||||
assert resp.status_code == 200
|
||||
return mock_set
|
||||
|
||||
|
||||
def test_position_writes_trail_point_with_ttl():
|
||||
_override({"node": True, "node_id": "node-1"})
|
||||
mock_set = _post_adsb([{"icao": "A1B2C3", "lat": 41.1, "lon": -73.8, "altitude_ft": 3000}])
|
||||
trail = _writes_to(mock_set, "aircraft/A1B2C3/positions")
|
||||
assert len(trail) == 1
|
||||
(_, doc_id, point), _ = trail[0]
|
||||
assert doc_id.isdigit()
|
||||
assert (point["lat"], point["lon"], point["altitude_ft"]) == (41.1, -73.8, 3000)
|
||||
assert point["expire_at"] > telemetry.datetime.now(telemetry.timezone.utc)
|
||||
|
||||
|
||||
def test_unchanged_position_is_not_rewritten_to_trail():
|
||||
_override({"node": True, "node_id": "node-1"})
|
||||
_post_adsb([{"icao": "A1B2C3", "lat": 41.1, "lon": -73.8}])
|
||||
again = _post_adsb([{"icao": "A1B2C3", "lat": 41.1, "lon": -73.8}])
|
||||
moved = _post_adsb([{"icao": "A1B2C3", "lat": 41.2, "lon": -73.8}])
|
||||
assert _writes_to(again, "aircraft/A1B2C3/positions") == []
|
||||
assert len(_writes_to(moved, "aircraft/A1B2C3/positions")) == 1
|
||||
|
||||
|
||||
def test_aircraft_without_position_gets_no_trail_point():
|
||||
_override({"node": True, "node_id": "node-1"})
|
||||
mock_set = _post_adsb([{"icao": "A1B2C3", "callsign": "UAL123"}])
|
||||
assert _writes_to(mock_set, "aircraft/A1B2C3/positions") == []
|
||||
assert len(_writes_to(mock_set, "aircraft")) == 1
|
||||
|
||||
Reference in New Issue
Block a user