178 lines
5.4 KiB
Python
Executable file
178 lines
5.4 KiB
Python
Executable file
# SPDX-FileCopyrightText: 2020 Carter Nelson for Adafruit Industries
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
import time
|
|
import math
|
|
import board
|
|
import displayio
|
|
import terminalio
|
|
from simpleio import map_range
|
|
import adafruit_imageload
|
|
from adafruit_pyportal import PyPortal
|
|
from adafruit_display_text.label import Label
|
|
from adafruit_display_shapes.line import Line
|
|
|
|
# --| User Config |---------------------------------------------------
|
|
UPDATE_RATE = 60 # minutes
|
|
MAX_STORMS = 3 # limit storms
|
|
NAME_COLOR = 0xFFFFFF # label text color
|
|
NAME_BG_COLOR = 0x000000 # label background color
|
|
ARROW_COLOR = 0x0000FF # movement direction arrow color
|
|
ARROW_LENGTH = 15 # movement direction arrow length
|
|
LAT_RANGE = (45, 5) # set to match map
|
|
LON_RANGE = (-100, -40) # set to match map
|
|
# --------------------------------------------------------------------
|
|
|
|
URL = "https://www.nhc.noaa.gov/CurrentStorms.json"
|
|
JSON_PATH = ["activeStorms"]
|
|
|
|
# setup pyportal
|
|
pyportal = PyPortal(
|
|
status_neopixel=board.NEOPIXEL,
|
|
default_bg="/map.bmp",
|
|
)
|
|
|
|
# setup display group for storms
|
|
icons_bmp, icons_pal = adafruit_imageload.load(
|
|
"/storm_icons.bmp", bitmap=displayio.Bitmap, palette=displayio.Palette
|
|
)
|
|
for i, c in enumerate(icons_pal):
|
|
if c == 0xFFFF00:
|
|
icons_pal.make_transparent(i)
|
|
storm_icons = displayio.Group()
|
|
pyportal.splash.append(storm_icons)
|
|
STORM_CLASS = ("TD", "TS", "HU")
|
|
|
|
# setup info label
|
|
info_update = Label(
|
|
terminalio.FONT,
|
|
text="1984-01-01T00:00:00.000Z",
|
|
color=NAME_COLOR,
|
|
background_color=NAME_BG_COLOR,
|
|
)
|
|
info_update.anchor_point = (0.0, 1.0)
|
|
info_update.anchored_position = (10, board.DISPLAY.height - 10)
|
|
pyportal.splash.append(info_update)
|
|
|
|
# these are need for lat/lon to screen x/y mapping
|
|
VIRTUAL_WIDTH = board.DISPLAY.width * 360 / (LON_RANGE[1] - LON_RANGE[0])
|
|
VIRTUAL_HEIGHT = board.DISPLAY.height * 360 / (LAT_RANGE[0] - LAT_RANGE[1])
|
|
Y_OFFSET = math.radians(LAT_RANGE[0])
|
|
Y_OFFSET = math.tan(math.pi / 4 + Y_OFFSET / 2)
|
|
Y_OFFSET = math.log(Y_OFFSET)
|
|
Y_OFFSET = (VIRTUAL_WIDTH * Y_OFFSET) / (2 * math.pi)
|
|
Y_OFFSET = VIRTUAL_HEIGHT / 2 - Y_OFFSET
|
|
|
|
|
|
def update_display():
|
|
# pylint: disable=too-many-locals
|
|
# clear out existing icons
|
|
while len(storm_icons):
|
|
_ = storm_icons.pop()
|
|
|
|
# get latest storm data
|
|
try:
|
|
resp = pyportal.network.fetch(URL)
|
|
storm_data = pyportal.network.process_json(resp.json(), (JSON_PATH,))[0]
|
|
except RuntimeError:
|
|
return
|
|
print("Number of storms:", len(storm_data))
|
|
|
|
# parse the storm data
|
|
for storm in storm_data:
|
|
# don't exceed max
|
|
if len(storm_icons) >= MAX_STORMS:
|
|
continue
|
|
# get lat/lon
|
|
lat = storm["latitudeNumeric"]
|
|
lon = storm["longitudeNumeric"]
|
|
# check if on map
|
|
if (
|
|
not LAT_RANGE[0] >= lat >= LAT_RANGE[1]
|
|
or not LON_RANGE[0] <= lon <= LON_RANGE[1]
|
|
):
|
|
continue
|
|
# OK, let's make a group for all the graphics
|
|
storm_gfx = displayio.Group()
|
|
# convert to sreen coords
|
|
x = int(map_range(lon, LON_RANGE[0], LON_RANGE[1], 0, board.DISPLAY.width - 1))
|
|
y = math.radians(lat)
|
|
y = math.tan(math.pi / 4 + y / 2)
|
|
y = math.log(y)
|
|
y = (VIRTUAL_WIDTH * y) / (2 * math.pi)
|
|
y = VIRTUAL_HEIGHT / 2 - y
|
|
y = int(y - Y_OFFSET)
|
|
# icon type
|
|
if storm["classification"] in STORM_CLASS:
|
|
storm_type = STORM_CLASS.index(storm["classification"])
|
|
else:
|
|
storm_type = 0
|
|
# create storm icon
|
|
icon = displayio.TileGrid(
|
|
icons_bmp,
|
|
pixel_shader=icons_pal,
|
|
width=1,
|
|
height=1,
|
|
tile_width=16,
|
|
tile_height=16,
|
|
default_tile=storm_type,
|
|
x=x - 8,
|
|
y=y - 8,
|
|
)
|
|
# add storm icon
|
|
storm_gfx.append(icon)
|
|
# add a label
|
|
name = Label(
|
|
terminalio.FONT,
|
|
text=storm["name"],
|
|
color=NAME_COLOR,
|
|
background_color=NAME_BG_COLOR,
|
|
)
|
|
name.anchor_point = (0.0, 1.0)
|
|
name.anchored_position = (x + 8, y - 8)
|
|
storm_gfx.append(name)
|
|
# add direction arrow
|
|
angle = math.radians(storm["movementDir"])
|
|
xd = x + int(ARROW_LENGTH * math.sin(angle))
|
|
yd = y - int(ARROW_LENGTH * math.cos(angle))
|
|
arrow = Line(x, y, xd, yd, color=ARROW_COLOR)
|
|
storm_gfx.append(arrow)
|
|
# add the storm graphics
|
|
storm_icons.append(storm_gfx)
|
|
# update time
|
|
info_update.text = storm["lastUpdate"]
|
|
# debug
|
|
print(
|
|
"{} @ {},{}".format(
|
|
storm["name"], storm["latitudeNumeric"], storm["longitudeNumeric"]
|
|
)
|
|
)
|
|
|
|
# no storms? at least say something
|
|
if not len(storm_icons):
|
|
print("No storms in map area.")
|
|
storm_icons.append(
|
|
Label(
|
|
terminalio.FONT,
|
|
scale=4,
|
|
x=50,
|
|
y=110,
|
|
text="NO STORMS\n IN AREA",
|
|
color=NAME_COLOR,
|
|
background_color=NAME_BG_COLOR,
|
|
)
|
|
)
|
|
|
|
|
|
# --------------------------------------------------------------------
|
|
# M A I N
|
|
# --------------------------------------------------------------------
|
|
update_display()
|
|
last_update = time.monotonic()
|
|
while True:
|
|
now = time.monotonic()
|
|
if now - last_update > UPDATE_RATE * 60:
|
|
print("Updating...")
|
|
update_display()
|
|
last_update = now
|