Files
StarPilot/openpilot/selfdrive/navigation/mapsd.py
T
2026-10-20 02:19:24 +00:00

85 lines
3.1 KiB
Python
Executable File

#!/usr/bin/env python3
import time
import numpy as np
import openpilot.cereal.messaging as messaging
from msgq.visionipc import VisionIpcServer
from openpilot.cereal.visionipc import VisionStreamType
from openpilot.common.realtime import Ratekeeper
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.navigation.helpers import latest_gps_source
from openpilot.selfdrive.navigation.pmtiles import PMTiles
from openpilot.selfdrive.navigation.renderer import SIZE, MapRenderer
from openpilot.selfdrive.navigation.route import RouteTracker
from openpilot.selfdrive.navigation.tiles import RemoteTiles
RETRY_SECONDS = 10
class MapsD:
def __init__(self):
self.renderer = None
self.tracker = None
self.route = np.empty((0, 2))
self.prefetched = False
self.retry_at = 0.0
def update(self, sm) -> tuple[np.ndarray, int]:
"""The map frame for the latest messages: black with a zero timestamp without a usable map, never stale."""
if sm.updated['navRoute']:
route = np.array([(c.longitude, c.latitude) for c in sm['navRoute'].coordinates], dtype=np.float64).reshape((-1, 2))
if not np.array_equal(route, self.route):
self.route = route
self.tracker = RouteTracker(route) if len(route) >= 2 else None # progress along the route starts over
self.prefetched = False
now = time.monotonic()
source = latest_gps_source(sm)
try:
if self.tracker is not None and now >= self.retry_at:
if self.renderer is None:
self.renderer = MapRenderer(PMTiles(RemoteTiles()))
if not self.prefetched:
# TODO: fetch the tiles more intelligently on demand
# Every tile the route can show is fetched up front, so each render below is a cache hit.
self.renderer.prefetch(self.route)
self.prefetched = True
if source is not None:
gps = sm[source]
# As in training, the map is centered on the route and headed along it, not at the GPS fix.
image, valid = self.renderer.render(*self.tracker.snap(gps.longitude, gps.latitude), self.route)
if valid:
return image, sm.logMonoTime[source]
except Exception: # a failed fetch, or a corrupt cached tile
cloudlog.exception('mapsd.render_failed')
self.close() # a changed archive needs a fresh reader
self.retry_at = now + RETRY_SECONDS
return np.zeros((SIZE, SIZE), dtype=np.uint8), 0
def close(self):
if self.renderer is not None:
self.renderer.archive.close()
self.renderer = None
self.prefetched = False
def main():
rk = Ratekeeper(20.0)
sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal', 'navRoute'])
server = VisionIpcServer('mapsd')
server.create_buffers_with_sizes(VisionStreamType.VISION_STREAM_MAP, 4, SIZE, SIZE, SIZE * SIZE, SIZE, SIZE * SIZE)
server.start_listener()
mapsd = MapsD()
try:
while True:
sm.update(0)
image, timestamp = mapsd.update(sm)
server.send(VisionStreamType.VISION_STREAM_MAP, image.tobytes(), rk.frame, timestamp, time.monotonic_ns())
rk.keep_time()
finally:
mapsd.close()
if __name__ == '__main__':
main()