# 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