Skip to content

Instantly share code, notes, and snippets.

@jesseadams
Created January 29, 2026 00:38
Show Gist options
  • Select an option

  • Save jesseadams/ad5a4a707bd2d2fc08d7c6bed3f2f510 to your computer and use it in GitHub Desktop.

Select an option

Save jesseadams/ad5a4a707bd2d2fc08d7c6bed3f2f510 to your computer and use it in GitHub Desktop.
Adafruit ESP32-S3 Feather with GPS Wing and 3.5" 480x320 TFT
# SPDX-FileCopyrightText: 2024
# SPDX-License-Identifier: MIT
import time
import board
from adafruit_display_text.label import Label
from displayio import Group
from terminalio import FONT
import busio
import adafruit_gps
import supervisor
import adafruit_max1704x
import rtc
import displayio
import fourwire
import adafruit_hx8357
import adafruit_tsc2007
import adafruit_st7789
import os
import storage
import adafruit_sdcard
from digitalio import DigitalInOut
#set_time = time.struct_time((2026, 1, 25, 6, 41, 0, -1, -1, -1))
# Get the onboard RTC
#r = rtc.RTC()
# Set the RTC's time
#r.datetime = set_time
#print("RTC time set to:", r.datetime)
print(board)
# 3.5" TFT FeatherWing V2
# Release any resources currently in use for the displays
displayio.release_displays()
# Use Hardware SPI
spi = board.SPI()
tft_cs = board.D9
tft_dc = board.D10
display_width = 480
display_height = 320
display_bus = fourwire.FourWire(spi, command=tft_dc, chip_select=tft_cs)
display = adafruit_hx8357.HX8357(display_bus, width=display_width, height=display_height)
# Initialize I2C - the TFT FeatherWing should have pull-ups
# If this fails, there may be a hardware issue
print("Initializing I2C...")
i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
print("I2C initialized")
irq_dio = None
print("Initializing touchscreen...")
tsc = adafruit_tsc2007.TSC2007(i2c, irq=irq_dio)
print("Touchscreen initialized")
# Mount SD card
sd_cs = DigitalInOut(board.D5) # SD card chip select on TFT FeatherWing
try:
sdcard = adafruit_sdcard.SDCard(spi, sd_cs)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, "/sd")
print("SD card mounted at /sd")
sd_files = os.listdir('/sd')
print(f"SD card files: {len(sd_files)} files")
except Exception as e:
print(f"SD card mount failed: {e}")
print("Continuing without SD card...")
# Initialize groups and labels for display
groups = []
text_labels = []
index = 0
touch_state = False
supervisor.runtime.autoreload = False
monitor = adafruit_max1704x.MAX17048(i2c)
# import busio
uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10)
# i2c = board.I2C() # uses board.SCL and board.SDA
# i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector
# Create a GPS module instance.
gps = adafruit_gps.GPS(uart, debug=False) # Use UART
# gps = adafruit_gps.GPS_GtopI2C(i2c, debug=False) # Use I2C interface
# Turn on the basic GGA and RMC info (what you typically want)
gps.send_command(b"PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0")
# Set update rate to once a second 1hz (what you typically want)
gps.send_command(b"PMTK220,1000")
last_print = time.monotonic()
last_console_print = time.monotonic()
# Begin main loop
from adafruit_datetime import date, datetime
import math
# Map drawing function
def draw_map_marker(group, x, y, color=0xFF0000):
"""Draw a simple marker on the map at given pixel coordinates"""
from vectorio import Circle, Polygon
from adafruit_display_shapes.circle import Circle as ShapeCircle
# Draw a simple circle marker
marker = ShapeCircle(x, y, 8, fill=color, outline=0xFFFFFF)
return marker
# Convert lat/lon to pixel coordinates (simple mercator-like projection)
def latlon_to_pixels(lat, lon, map_center_lat, map_center_lon, zoom_level=14):
"""Convert lat/lon to pixel offsets from center of display"""
# Simple conversion - adjust scale based on zoom
pixels_per_degree = zoom_level * 10
dx = (lon - map_center_lon) * pixels_per_degree * math.cos(math.radians(map_center_lat))
dy = (map_center_lat - lat) * pixels_per_degree
# Center on display
x = display_width // 2 + int(dx)
y = display_height // 2 + int(dy)
return x, y
# ============================================================================
# DYNAMIC GPS-BASED TILE LOADING SYSTEM
# ============================================================================
# Create a map display group
map_group = displayio.Group()
# Configuration
TILES_PER_LOCATION = len([13, 14, 15, 16]) # Four zoom levels
current_zoom = 15
available_zoom_levels = [13, 14, 15, 16] # Zoom levels to cache
MARKER_SIZE = 12 # GPS marker size in pixels
# Tile cache: {filename: (bitmap, palette, tilegrid)}
tile_cache = {}
cache_order = [] # Track LRU
current_tile_filename = None
current_location_tiles = {} # Store all zoom levels for current location: {zoom: filename}
def find_tile_for_position(lat, lon, zoom):
"""Find the best tile filename for given GPS position"""
# Zoom 17 uses 0.001 degree grid, others use 0.01 degree grid
if zoom >= 17:
lat_h = math.floor(lat * 1000)
lon_h = math.floor(lon * 1000)
scale = 1000
format_str = "03d"
else:
lat_h = math.floor(lat * 100)
lon_h = math.floor(lon * 100)
scale = 100
format_str = "02d"
lat_snapped = lat_h / scale
lon_snapped = lon_h / scale
tile_center_lat = (lat_h + 0.5) / scale
tile_center_lon = (lon_h + 0.5) / scale
# Create filename
lat_dir = 'N' if lat_h >= 0 else 'S'
lon_dir = 'E' if lon_h >= 0 else 'W'
lat_abs = abs(lat_h)
lon_abs = abs(lon_h)
if zoom >= 17:
lat_str = f"{lat_abs // scale}_{lat_abs % scale:{format_str}}"
lon_str = f"{lon_abs // scale}_{lon_abs % scale:{format_str}}"
else:
lat_str = f"{lat_abs // scale}_{lat_abs % scale:{format_str}}0"
lon_str = f"{lon_abs // scale}_{lon_abs % scale:{format_str}}0"
filename = f"/sd/map_z{zoom}_lat{lat_dir}{lat_str}_lon{lon_dir}{lon_str}.bmp"
print(f"Looking for tile: {filename}")
# Check if file exists
try:
os.stat(filename)
return filename
except:
# Try nearby tiles
for lat_offset in [-0.01, 0, 0.01]:
for lon_offset in [-0.01, 0, 0.01]:
test_lat = round(lat + lat_offset, 2)
test_lon = round(lon + lon_offset, 2)
lat_dir = 'N' if test_lat >= 0 else 'S'
lon_dir = 'E' if test_lon >= 0 else 'W'
lat_abs = abs(test_lat)
lon_abs = abs(test_lon)
lat_str = f"{lat_abs:.4f}".replace('.', '_')
lon_str = f"{lon_abs:.4f}".replace('.', '_')
test_filename = f"/sd/map_z{zoom}_lat{lat_dir}{lat_str}_lon{lon_dir}{lon_str}.bmp"
try:
os.stat(test_filename)
return test_filename
except:
pass
return None
def load_tile_from_sd(filename):
"""Load a tile from SD card into RAM"""
if filename in tile_cache:
return tile_cache[filename]
print(f"Loading tile: {filename}")
try:
with open(filename, 'rb') as f:
# Read BMP header
f.seek(10)
data_offset = int.from_bytes(f.read(4), 'little')
f.seek(18)
width = int.from_bytes(f.read(4), 'little')
height = int.from_bytes(f.read(4), 'little')
f.seek(28)
bits_per_pixel = int.from_bytes(f.read(2), 'little')
if bits_per_pixel == 1:
# 1-bit black and white
palette = displayio.Palette(2)
palette[0] = 0x000000 # Black
palette[1] = 0xFFFFFF # White
# Read pixel data
f.seek(data_offset)
pixel_data = bytearray(f.read())
# Create bitmap in RAM
bitmap = displayio.Bitmap(width, height, 2)
row_size = ((width + 31) // 32) * 4 # 1-bit rows padded to 4 bytes
for y in range(height):
src_y = height - 1 - y
for x in range(width):
byte_idx = src_y * row_size + x // 8
bit_idx = 7 - (x % 8)
bitmap[x, y] = (pixel_data[byte_idx] >> bit_idx) & 1
# Create TileGrid
tilegrid = displayio.TileGrid(bitmap, pixel_shader=palette)
# Add to cache
tile_cache[filename] = (bitmap, palette, tilegrid)
print(f" ✓ Loaded into RAM")
return tile_cache[filename]
elif bits_per_pixel == 8 or bits_per_pixel == 4:
# Read palette (256 colors for 8-bit, 16 colors for 4-bit)
palette_size = 256 if bits_per_pixel == 8 else 16
f.seek(54)
palette_data = f.read(palette_size * 4)
palette = displayio.Palette(palette_size)
for i in range(palette_size):
b = palette_data[i*4]
g = palette_data[i*4+1]
r = palette_data[i*4+2]
palette[i] = (r << 16) | (g << 8) | b
# Read pixel data
f.seek(data_offset)
pixel_data = bytearray(f.read())
# Create bitmap in RAM
bitmap = displayio.Bitmap(width, height, palette_size)
if bits_per_pixel == 8:
row_size = ((width + 3) // 4) * 4
for y in range(height):
src_y = height - 1 - y
for x in range(width):
bitmap[x, y] = pixel_data[src_y * row_size + x]
else: # 4-bit
row_size = ((width + 7) // 8) * 4
for y in range(height):
src_y = height - 1 - y
for x in range(width):
byte_idx = src_y * row_size + x // 2
if x % 2 == 0:
bitmap[x, y] = pixel_data[byte_idx] >> 4
else:
bitmap[x, y] = pixel_data[byte_idx] & 0x0F
# Create TileGrid
tilegrid = displayio.TileGrid(bitmap, pixel_shader=palette)
# Add to cache
tile_cache[filename] = (bitmap, palette, tilegrid)
print(f" ✓ Loaded into RAM")
return tile_cache[filename]
except Exception as e:
print(f" Failed: {e}")
return None
def preload_all_zoom_levels(lat, lon, show_current_first=False):
"""Preload tiles for all zoom levels at current GPS position"""
global current_location_tiles, current_tile_filename
print(f"Preloading zoom levels for position ({lat:.4f}, {lon:.4f})...")
new_location_tiles = {}
loaded_count = 0
current_tile_data = None
# If show_current_first, load current zoom level first
if show_current_first:
filename = find_tile_for_position(lat, lon, current_zoom)
if filename:
new_location_tiles[current_zoom] = filename
if filename not in tile_cache:
tile_data = load_tile_from_sd(filename)
if tile_data:
loaded_count += 1
current_tile_data = tile_data
current_tile_filename = filename
print(f" ✓ Current zoom {current_zoom} loaded - ready to display!")
else:
current_tile_data = tile_cache[filename]
current_tile_filename = filename
print(f" Zoom {current_zoom}: Already in cache")
# Now load all other zoom levels
for zoom in available_zoom_levels:
if show_current_first and zoom == current_zoom:
continue # Already loaded above
filename = find_tile_for_position(lat, lon, zoom)
if filename:
new_location_tiles[zoom] = filename
# Load into cache if not already loaded
if filename not in tile_cache:
tile_data = load_tile_from_sd(filename)
if tile_data:
loaded_count += 1
else:
print(f" Zoom {zoom}: Already in cache")
else:
print(f" Zoom {zoom}: No tile found")
# Clear old location tiles from cache
for old_filename in current_location_tiles.values():
if old_filename in tile_cache and old_filename not in new_location_tiles.values():
del tile_cache[old_filename]
print(f" Evicted old tile: {old_filename}")
current_location_tiles = new_location_tiles
print(f"✓ Preloaded {loaded_count} new tiles, {len(tile_cache)} total in cache")
return current_tile_data
def update_map_for_gps(lat, lon, zoom):
"""Update the displayed map based on GPS position - uses cached tiles for instant zoom"""
global current_tile_filename
# Check if we have this zoom level cached for current location
if zoom in current_location_tiles:
filename = current_location_tiles[zoom]
if filename in tile_cache:
# Instant zoom - tile already in cache!
bitmap, palette, tilegrid = tile_cache[filename]
map_group[0] = tilegrid
current_tile_filename = filename
print(f"✓ Instant zoom to {zoom} (cached)")
return True
# Need to load new location - preload all zoom levels
filename = preload_all_zoom_levels(lat, lon)
if filename and filename in tile_cache:
bitmap, palette, tilegrid = tile_cache[filename]
map_group[0] = tilegrid
current_tile_filename = filename
print(f"Map updated for ({lat:.4f}, {lon:.4f}) zoom {zoom}")
return True
print(f"No tile found for ({lat:.4f}, {lon:.4f}) zoom {zoom}")
return False
def update_marker_position(gps_lat, gps_lon):
"""Update marker position based on GPS coordinates relative to tile grid"""
# Zoom 17 uses 0.001 degree grid, others use 0.01 degree grid
if current_zoom >= 17:
lat_h = math.floor(gps_lat * 1000)
lon_h = math.floor(gps_lon * 1000)
tc_lat = (lat_h + 0.5) / 1000.0
tc_lon = (lon_h + 0.5) / 1000.0
else:
lat_h = math.floor(gps_lat * 100)
lon_h = math.floor(gps_lon * 100)
tc_lat = (lat_h + 0.5) / 100.0
tc_lon = (lon_h + 0.5) / 100.0
# OSM tile calc (use current zoom)
lat_rad = math.radians(tc_lat)
n = 2.0 ** current_zoom
ctx = int((tc_lon + 180.0) / 360.0 * n)
tan_lat = math.tan(lat_rad)
asinh_tan = math.log(tan_lat + math.sqrt(tan_lat * tan_lat + 1))
cty = int((1.0 - asinh_tan / math.pi) / 2.0 * n)
# Canvas bounds (3x2 tiles)
sx = ctx - 1
sy = cty - 1
# sinh helper
def sinh(x):
ex = math.exp(x)
return (ex - 1.0/ex) / 2.0
# Canvas corners
def tile2lat(y):
return math.degrees(math.atan(sinh(math.pi * (1 - 2 * y / n))))
def tile2lon(x):
return x / n * 360.0 - 180.0
nw_lat = tile2lat(sy)
nw_lon = tile2lon(sx)
se_lat = tile2lat(sy + 2)
se_lon = tile2lon(sx + 3)
# Mercator Y helper
def merc_y(lat):
lr = math.radians(lat)
return math.log(math.tan(lr) + 1.0 / math.cos(lr))
# Center pixel in canvas
cx_frac = (tc_lon - nw_lon) / (se_lon - nw_lon)
cx_px = cx_frac * 768
cy_merc = merc_y(tc_lat)
nw_merc = merc_y(nw_lat)
se_merc = merc_y(se_lat)
cy_frac = (nw_merc - cy_merc) / (nw_merc - se_merc)
cy_px = cy_frac * 512
# GPS pixel in canvas
gx_frac = (gps_lon - nw_lon) / (se_lon - nw_lon)
gx_px = gx_frac * 768
gy_merc = merc_y(gps_lat)
gy_frac = (nw_merc - gy_merc) / (nw_merc - se_merc)
gy_px = gy_frac * 512
# Convert to tile coords
pixel_x = int(gx_px - (cx_px - 240))
pixel_y = int(gy_px - (cy_px - 160))
# Clamp and update marker
pixel_x = max(6, min(474, pixel_x))
pixel_y = max(6, min(314, pixel_y))
marker_grid.x = pixel_x - 6
marker_grid.y = pixel_y - 6
# Add GPS marker to map group (before GPS fix section)
print("Adding GPS marker...")
marker_bitmap = displayio.Bitmap(MARKER_SIZE, MARKER_SIZE, 2)
marker_palette = displayio.Palette(2)
marker_palette.make_transparent(0)
marker_palette[1] = 0xFF0000 # Red
for y in range(MARKER_SIZE):
for x in range(MARKER_SIZE):
if x == 0 or x == MARKER_SIZE-1 or y == 0 or y == MARKER_SIZE-1:
marker_bitmap[x, y] = 1
elif x > 2 and x < MARKER_SIZE-3 and y > 2 and y < MARKER_SIZE-3:
marker_bitmap[x, y] = 1
# Start at center - will be updated based on actual GPS position
marker_grid = displayio.TileGrid(
marker_bitmap,
pixel_shader=marker_palette,
x=display_width//2 - MARKER_SIZE//2,
y=display_height//2 - MARKER_SIZE//2
)
map_group.append(marker_grid)
print("Adding map label...")
map_label = Label(
FONT,
text="GPS MAP",
color=0xFFFFFF,
x=10,
y=10,
line_spacing=1.0,
background_color=0x000000
)
map_group.append(map_label)
supervisor.runtime.autoreload = False
monitor = adafruit_max1704x.MAX17048(i2c)
# PHASE 1: Wait for GPS fix before loading map
print("=== PHASE 1: Waiting for GPS fix ===")
# Create waiting screen
waiting_group = displayio.Group()
waiting_background = displayio.Bitmap(display_width, display_height, 2)
waiting_palette = displayio.Palette(2)
waiting_palette[0] = 0x000000 # Black background
waiting_palette[1] = 0xFFFFFF # White text
for y in range(display_height):
for x in range(display_width):
waiting_background[x, y] = 0
waiting_tile_grid = displayio.TileGrid(waiting_background, pixel_shader=waiting_palette)
waiting_group.append(waiting_tile_grid)
waiting_label = Label(FONT, text="Waiting for GPS fix...\n\nSearching for satellites...", color=0xFFFFFF, x=10, y=display_height//2 - 20, line_spacing=1.2)
waiting_group.append(waiting_label)
# Show waiting screen
display.root_group = waiting_group
print("Displaying 'Waiting for GPS fix' screen...")
# Wait for initial GPS fix
gps_fix_acquired = False
initial_lat = None
initial_lon = None
print("Waiting for GPS fix...")
start_wait_time = time.monotonic()
while not gps_fix_acquired:
gps.update()
if gps.has_fix:
initial_lat = gps.latitude
initial_lon = gps.longitude
gps_fix_acquired = True
print(f"✓ GPS fix acquired: ({initial_lat:.6f}, {initial_lon:.6f})")
print(f" Satellites: {gps.satellites}, Quality: {gps.fix_quality}")
# Update waiting screen to show we're loading map
waiting_label.text = f"GPS fix acquired!\n\nLat: {initial_lat:.6f}\nLon: {initial_lon:.6f}\n\nLoading map..."
time.sleep(0.5)
else:
# Update waiting screen
elapsed = int(time.monotonic() - start_wait_time)
waiting_label.text = f"Waiting for GPS fix...\n\nSearching for satellites...\n\nTime: {elapsed}s"
time.sleep(0.5)
# Now load the map tile for the GPS position and preload all zoom levels
print(f"=== Loading map tiles for GPS position ===")
# Step 1: Load current zoom level and display immediately
print(f"Loading zoom {current_zoom} for display...")
current_filename = find_tile_for_position(initial_lat, initial_lon, current_zoom)
if current_filename:
# Load directly from SD and display
try:
with open(current_filename, 'rb') as f:
bitmap = displayio.OnDiskBitmap(current_filename)
tilegrid = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)
# Insert at index 0 so marker and label appear on top
map_group.insert(0, tilegrid)
current_tile_filename = current_filename
print(f"✓ Map displayed from SD card")
# Update marker position for initial GPS coordinates
# Note: marker_grid will be created later, so we'll update it after display
initial_marker_lat = initial_lat
initial_marker_lon = initial_lon
# Show the map immediately
print("Adding map to groups...")
groups.append(map_group)
text_labels.append(map_label)
display.root_group = groups[0]
print("✓ Map is now visible on screen!")
# Update map label with initial status
t = datetime.now()
time_str = f"{t.hour:02d}:{t.minute:02d}:{t.second:02d}"
zoom_info = f"Zoom: {current_zoom}"
map_label.text = f"{time_str} GPS MAP\n{zoom_info}\nGPS: FIX\nLat: {initial_lat:.6f}\nLon: {initial_lon:.6f}\nLoading tiles..."
# Update marker position now that marker_grid exists
update_marker_position(initial_marker_lat, initial_marker_lon)
print(f"✓ Marker positioned at GPS coordinates")
# Set last GPS position for movement tracking
last_gps_lat = initial_lat
last_gps_lon = initial_lon
except Exception as e:
print(f"Failed to load initial tile: {e}")
current_filename = None
if current_filename:
# Step 2 & 3: Now preload all zoom levels into RAM (including current one)
print(f"Preloading all zoom levels into RAM...")
preload_all_zoom_levels(initial_lat, initial_lon, show_current_first=False)
# Replace the OnDiskBitmap with RAM version for faster access
if current_filename in tile_cache:
bitmap, palette, tilegrid = tile_cache[current_filename]
map_group[0] = tilegrid
print(f"✓ Switched to RAM-based tile for instant zooming")
else:
print(f"⚠ No tile found for initial position ({initial_lat:.4f}, {initial_lon:.4f})")
print("Creating placeholder...")
# Create placeholder
map_background = displayio.Bitmap(display_width, display_height, 2)
map_palette = displayio.Palette(2)
map_palette[0] = 0xE8F4E8
map_palette[1] = 0xFF0000
for y in range(display_height):
for x in range(display_width):
map_background[x, y] = 0
# Draw X
for i in range(min(display_width, display_height)):
map_background[i, i] = 1
map_background[display_width-1-i, i] = 1
map_tile_grid = displayio.TileGrid(map_background, pixel_shader=map_palette)
# Insert at index 0 so marker and label appear on top
map_group.insert(0, map_tile_grid)
# Track last GPS position to detect movement (will be set after GPS fix)
last_gps_lat = None
last_gps_lon = None
while True:
# Update GPS
gps.update()
# Update current position if we have GPS fix
if gps.has_fix:
current_lat = gps.latitude
current_lon = gps.longitude
# Only show speed if GPS reports it and it's above threshold
if gps.speed_knots and gps.speed_knots > 0.1:
current_speed = gps.speed_knots * 1.15078
else:
current_speed = 0.0
gps_has_fix = True
else:
# Keep last known position, just update status
gps_has_fix = False
current_speed = 0.0
# TFT FeatherWing - Handle touch for zoom
if tsc.touched and not touch_state:
point = tsc.touch
print("Touchpoint: (%d, %d, %d)" % (point["x"], point["y"], point["pressure"]))
x_coord = point["x"]
y_coord = point["y"]
# Top half = zoom in, bottom half = zoom out
if x_coord < 2000: # Top half (zoom in)
new_zoom_index = available_zoom_levels.index(current_zoom) if current_zoom in available_zoom_levels else 2
if new_zoom_index < len(available_zoom_levels) - 1:
current_zoom = available_zoom_levels[new_zoom_index + 1]
# Update map with new zoom
update_map_for_gps(current_lat, current_lon, current_zoom)
update_marker_position(current_lat, current_lon)
print(f"Zoomed in to level {current_zoom}")
else:
print(f"Already at max zoom {current_zoom}")
else: # Bottom half (zoom out)
new_zoom_index = available_zoom_levels.index(current_zoom) if current_zoom in available_zoom_levels else 2
if new_zoom_index > 0:
current_zoom = available_zoom_levels[new_zoom_index - 1]
# Update map with new zoom
update_map_for_gps(current_lat, current_lon, current_zoom)
update_marker_position(current_lat, current_lon)
print(f"Zoomed out to level {current_zoom}")
else:
print(f"Already at min zoom {current_zoom}")
touch_state = True
if not tsc.touched and touch_state:
touch_state = False
current = time.monotonic()
# Debug battery levels every 30 seconds
if current - last_console_print >= 30:
last_console_print = current
if monitor.cell_percent > 102:
print(f"Battery not connected")
else:
print(f"Battery voltage: {monitor.cell_voltage:.2f} Volts")
print(f"Battery percentage: {monitor.cell_percent:.1f} %")
# Update display data every 0.25 seconds (4 times per second)
if current - last_print >= 0.25:
last_print = current
# Make sure we're showing the map, not the waiting screen
if len(groups) > 0 and display.root_group != groups[index]:
display.root_group = groups[index]
if gps_has_fix:
fix_status = "GPS: FIX"
fix_quality = gps.fix_quality
satellites = gps.satellites
gps_info = f"Sats: {satellites}, Q: {fix_quality}"
else:
fix_status = "GPS: NO FIX (last known)"
gps_info = "Using last position"
gps_lat = current_lat
gps_lon = current_lon
gps_speed = current_speed
# Update marker position based on GPS coordinates
update_marker_position(gps_lat, gps_lon)
# Update map if GPS position changed significantly (>0.005 degrees ≈ 500m)
if gps_has_fix and last_gps_lat is not None and (abs(gps_lat - last_gps_lat) > 0.005 or abs(gps_lon - last_gps_lon) > 0.005):
update_map_for_gps(gps_lat, gps_lon, current_zoom)
last_gps_lat = gps_lat
last_gps_lon = gps_lon
t = datetime.now()
time_str = f"{t.hour:02d}:{t.minute:02d}:{t.second:02d}"
if monitor.cell_percent > 102:
power_text = "Battery: Not connected"
else:
power_text = f"Battery: {monitor.cell_percent:.1f}%"
# Update map label
zoom_info = f"Zoom: {current_zoom} ({available_zoom_levels[0]}-{available_zoom_levels[-1]})"
cache_info = f"Tiles: {len(tile_cache)}"
map_label.text = f"{time_str} GPS MAP\n{zoom_info} {cache_info}\n{fix_status} - {gps_info}\nLat: {gps_lat:.6f}\nLon: {gps_lon:.6f}\nSpeed: {gps_speed:.1f} mph\n{power_text}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment