Skip to content

Commit 199097b

Browse files
committed
Add aircraft info touch display
1 parent fcd4a8f commit 199097b

File tree

2 files changed

+55
-7
lines changed

2 files changed

+55
-7
lines changed

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ A collection of functionality-related constants is specified in `skyportal_confi
6969
| `GEO_ALTITUDE_THRESHOLD_M` | Skip drawing aircraft below this GPS altitude, meters | `20` |
7070

7171
## Touchscreen Functionality
72-
**NOTE:** Due to the lack of an available asynchronous requests library for CircuitPython, the call to the OpenSky API is blocking and will block touchscreen functionality until a response is obtained. An attempt will be made to reflect the current blocking status in all UI elements, indicating to the user that their touch inputs can't be processed.
72+
**NOTE:** Due to the lack of an available asynchronous requests library for CircuitPython, the call to the OpenSky API is blocking and will block touchscreen functionality until a response is obtained. An attempt is made to reflect the current blocking status in all UI elements, indicating to the user that their touch inputs can't be processed.
73+
74+
### Aircraft Information
75+
Tapping on an aircraft icon will display state information for the aircraft closest to the registered touch point.
7376

7477
### Screenshot
7578
If enabled in the SkyPortal configuration file, a screenshot button is created in the lower left, allowing the user to take a screenshot to SD card storage. The device utilizes a rolling storage, keeping the `n` most recent screenshots and discarding the oldest screenshot if above this threshold.

skyportal/displaylib.py

+51-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import math
12
import os
23
from collections import namedtuple
34

@@ -320,6 +321,8 @@ class SkyPortalUI: # noqa: D101
320321

321322
grid_bounds: tuple[float, float, float, float]
322323

324+
_aircraft_positions: dict[tuple[int, int], AircraftState]
325+
323326
def __init__(self, enable_screenshot: bool = False) -> None:
324327
# Set up main display element
325328
self.main_display_group = displayio.Group()
@@ -333,6 +336,7 @@ def __init__(self, enable_screenshot: bool = False) -> None:
333336
self.screenshot_handler = ScreenshotHandler()
334337

335338
self.aircraft_info = AircraftInfoBox()
339+
self._aircraft_positions = {}
336340

337341
self._enable_screenshot = enable_screenshot
338342

@@ -436,6 +440,7 @@ def draw_aircraft(
436440
NOTE: Aircraft icons are not drawn if an aircraft's state vector is missing the required
437441
position & orientation data.
438442
"""
443+
self._aircraft_positions = {}
439444
while len(self.aircraft_display_group):
440445
self.aircraft_display_group.pop()
441446

@@ -474,11 +479,40 @@ def draw_aircraft(
474479
)
475480
self.aircraft_display_group.append(icon)
476481

482+
# Cache the pixel locations so we can populate the info box on tap
483+
self._aircraft_positions[(icon_x, icon_y)] = ap
484+
477485
n_skipped = n_unplottable + n_ground
478-
print(
479-
f"Skipped drawing {n_skipped} aircraft ({n_unplottable} missing data, {n_ground} on ground)" # noqa: E501
486+
print(f"Skipped {n_skipped} aircraft ({n_unplottable} missing data, {n_ground} on ground)")
487+
488+
def _closest_aircraft(
489+
self,
490+
touch_coord: tuple[int, int],
491+
threshold_px: int = 30,
492+
) -> t.Optional[AircraftState]:
493+
"""
494+
Locate the aircraft icon closest to the provided touch point.
495+
496+
If no aircraft are plotted, or the closest aircraft is further from the touch point than the
497+
specified pixel threshold, then `None` is returned.
498+
"""
499+
if not self._aircraft_positions:
500+
return None
501+
502+
dists = sorted(
503+
(
504+
(ac_state, dist(ac_loc, touch_coord))
505+
for ac_loc, ac_state in self._aircraft_positions.items()
506+
),
507+
key=lambda x: x[1],
480508
)
481509

510+
closest_ac, px_dist = dists[0]
511+
if px_dist > threshold_px:
512+
return None
513+
else:
514+
return closest_ac
515+
482516
def touch_on(self) -> None: # noqa: D102
483517
if self._enable_screenshot:
484518
self.screenshot_buttons[True].tilegrid.hidden = False
@@ -491,14 +525,25 @@ def touch_off(self) -> None: # noqa: D102
491525

492526
def process_touch(self, touch_coord: tuple[int, int, int]) -> None:
493527
"""Process the provided touch input coordinate & fire the first action required."""
528+
touch_x, touch_y, _ = touch_coord
494529
if self._enable_screenshot:
495530
self.screenshot_buttons[True].check_fire(touch_coord)
496531
return
497532

498533
if self.aircraft_info.hidden:
499-
print("Would search for closest aircraft here, then display it.")
500-
self.aircraft_info.hidden = False
501-
return
534+
closest_ac = self._closest_aircraft((touch_x, touch_y))
535+
if closest_ac is not None:
536+
self.aircraft_info.set_aircraft_info(closest_ac)
537+
self.aircraft_info.hidden = False
502538
else:
503-
print("Closing aircraft info popup")
504539
self.aircraft_info.hidden = True
540+
541+
542+
def dist(p: tuple[int, int], q: tuple[int, int]) -> float:
543+
"""
544+
Return the Euclidean distance between points `p` and `q`.
545+
546+
Taken from https://docs.python.org/3/library/math.html#math.dist since CircuitPython's `math`
547+
library doesn't have this yet.
548+
"""
549+
return math.sqrt(sum((px - qx) ** 2.0 for px, qx in zip(p, q)))

0 commit comments

Comments
 (0)