Skip to content

Add Maps app (first version, tested on T-Deck only) - #65

Open
AdaSzi wants to merge 3 commits into
TactilityProject:mainfrom
AdaSzi:maps
Open

AdaSzi wants to merge 3 commits into
TactilityProject:mainfrom
AdaSzi:maps

Conversation

@AdaSzi

@AdaSzi AdaSzi commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Changelog

0.2.0

Labels, map rotation and heading-up, plus menu, list, trackball and download fixes.

Labels

  • Place, street, river, water and peak names, placed for each view so they never overlap; street names
    follow the street and stay upright; a light halo.
  • Settings → Labels (Off / Few / Normal), Settings → Latin names (the Latin-script name where the map data
    has one). Names in scripts the label font has no letters for (Japanese, Chinese, Arabic and others) always
    show their Latin-script name.
  • Label fonts: the app ships a subset of Adwaita Sans with Latin (Extended-A and -B), Greek, Cyrillic and
    Vietnamese letters, so names like Štefánikova, Łódź, İstanbul, Москва or Hà Nội show fully; the device
    makes the fonts from it once (about 7 s in the background on first start) and keeps them on the card.
  • Map data downloaded with 0.1.0 is upgraded in place in idle time, the tiles on screen first; no download
    is needed.

Rotation

  • Q / E turn the map by 15°, N or the compass (top right, only shown on a turned map) turns it back to
    north; two fingers turn it once twisted past 45° (15° when already turned), so pinching does not turn it
    by accident.
  • ⌖ / G now cycles: follow north up → follow with the direction of travel up (from the GPS course while
    moving faster than about 1 m/s, smoothed, held when slower) → not following.
  • Keys and dragging move a turned map in screen directions; labels stay upright; the download frame always
    works north up.
  • On monochrome and e-paper displays turning is not animated.

Menu and lists

  • Menu → Location switches Tactility's location service on or off (with a message).
  • Enter or a tap on a row opens its actions; lists have a back arrow and a check mark in their header,
    swipe right goes back; lists open without a highlight until a key, the trackball or the encoder moves.
  • Menu names: Download map (Choose area, Choose region), Downloaded maps.

Trackball and touch

  • The trackball works as a mouse pointer on every screen, with acceleration; Settings → Pointer speed
    (Slow / Normal / Fast).
  • A click on the map shows or hides the controls; holding the button no longer opens the menu. Dragging a
    list scrolls it.

Waypoints

  • A sheet to name a new waypoint (hold the map, hold ⌖, P, or Menu → Add waypoint); typing replaces the
    suggested name. All pins are the same size.

Downloads

  • A progress pill with tiles and percentage, and a details card (tap the pill) with Pause, Resume and
    Cancel, speed and time left.
  • Detail fetched while browsing: tiles no longer needed after you move on are dropped from the queue (the
    one downloading finishes).
  • A download that fails (network or memory) tries again by itself after 30 s, up to 3 times; the message
    stays 10 s.
  • A framed area is named after the nearest town's Latin-script name, so the lists can show it.
  • Framing an area a region already covers does not download it again: an unfinished region continues, a
    finished one says so.

Fixes

  • The map could stop responding for many seconds while big tiles loaded.
  • Zooming out kept a blank screen until tiles loaded (the previous map now stays, shrunk).
  • Browsing downloads continued after panning only after a zoom change.
  • The progress pill stayed after browsing ended.
  • A large low-zoom tile is read from the card once instead of once per zoomed-in tile.
  • Downloads failed late in a session: the large low-zoom tiles (over 1 MB) needed one free block of memory
    that size. Tiles now download in 64 KB pieces, and the tile cache lends memory when even a piece does not fit.

0.1.0

Idle updates, named regions, waypoints, track recording and GPX files, plus smooth zoom.

Zoom

    • / −, I / O and double tap zoom smoothly (about a quarter of a second, around the screen centre or the
      tapped point), then the sharp map appears. Pinch scales live as before.
  • Off on monochrome and e-paper displays, or with Settings → Smooth zoom off (renamed from Smooth pinch).

Regions and downloads

  • Download area asks "This area" or "Choose region". Choose region lists continents, countries and regions
    from Geofabrik's list, shipped with the app. "All of …" downloads a whole region.
  • The highest zoom level that fits OpenFreeMap's 3,000-tile / 50 MB limit is chosen automatically, with the
    tile count, size and zoom shown before you confirm. Only tiles inside the region's outline are fetched.
  • Downloads get names: a chosen region its own name, a framed area the nearest town once downloaded. The
    Regions screen shows the names; R renames (keyboard). Folders keep 8.3 names.
  • Outdated regions update quietly after about 30 s without input, one tile at a time, the tiles you look at
    first, with an "Updating 42%" label. Any input pauses it and it continues later, also after a restart.
    Only on Wi-Fi, and while charging or above 40 % battery.
  • Enter or tap on an outdated region offers "Update now". The region "browsed" is not updated.
  • HTTPS connections are kept open and shared, so a new download or browsing no longer pays a 2-3 s TLS
    handshake each time.

Waypoints

  • P, holding ⌖, Menu → Add waypoint, or holding the map on touch screens saves a waypoint. Named WP 001,
    WP 002, …, shown as pins at every zoom level.
  • Tap a pin for name, coordinates and distance and direction from you. Menu → Waypoints lists them by
    distance; Enter or tap goes there, R renames, Del or hold deletes after asking.
  • Kept in waypoints.gpx (GPX 1.1) in the app's folder on the card.

Tracks

  • Menu → Record track: start, pause, resume and stop. A red ● with the time shows top right, with the
    distance when the controls are shown.
  • Points every 5 m (or more than the GPS accuracy) or every 30 s while moving; approximate positions are
    skipped. The track is drawn as a red line.
  • Written to tracks/YYYYMMDD-HHMM.gpx every 30 s, so a crash loses at most 30 s. "Recorded tracks" lists
    the files with their folder.
  • Recording only runs while the app is open.

GPX files

  • Copy .gpx files into /sdcard/gpx. Menu → GPX files shows or hides them.
  • Tracks blue, routes purple, waypoints as small dots. A compact .gpc copy is kept next to each file.
    A 50,000-point track was tested on the PC, not yet on the device.

Trackball

  • In mouse mode the pointer shows only while the controls or the menu are open, the button under it is
    highlighted, and it hides after 3 s without movement.
  • A click brings it back, and a click on the map while the controls are hidden shows them.

Displays

  • A black-and-white style for 1-bit displays: black roads with white edges, water and parks as sparse
    patterns, outlined buildings, no grey. Checked in renders on the PC only.

Requirements

  • Needs a board with 8 MB PSRAM for now.

0.0.1 (proof of concept)

The first public version: an offline vector map for Tactility, tested on the LilyGO T-Deck.

Map

  • Fullscreen map drawn by the app with LVGL stopped. LVGL comes back on every exit.
  • OpenFreeMap vector tiles (OpenMapTiles schema) from the SD card: water, land cover and use, parks, buildings,
    waterways, roads with casings, railways, boundaries and airports, anti-aliased.
  • Zoom 0-19. Levels above 14 are drawn from the z14 vectors, so they stay sharp.
  • Pan and zoom by keys (W A S D, I / O), trackball, drag, pinch, double tap and the + / − buttons.
  • Your position from Tactility's location service, with an accuracy circle, a direction arrow while moving and
    follow mode (G or ⌖). The app never changes location settings.
  • Scale bar, and the attribution: shown in full for 5 s, then a © mark that opens it again and leads to
    About and licences.
  • Hidden controls (☰, + / −, ⌖) that show with a tap, Space or a click, with key hints on keyboards and a
    pointer for the trackball in mouse mode.
  • Opens at ESA ESTEC the first time, then where you left it.

Downloads

  • Download area: a fixed frame in the middle, the map moves and zooms under it. A live tile count, size estimate
    and the highest zoom that fits OpenFreeMap's 3,000-tile / 50 MB limit.
  • The downloader uses OpenFreeMap's TileJSON, 2 keep-alive HTTPS connections and its own User-Agent. Tiles are
    written as .tmp and renamed. Downloads resume after a restart, areas share tiles, and the download stops at
    50 MB of real data.
  • Download while browsing: with Wi-Fi, tiles on screen that have no detail yet are fetched one at a time and kept
    in the region "browsed".
  • Regions screen: size, date and status, with delete (tiles other regions use are kept).

Speed

  • Prepared tiles (.prt) next to each .pbf: only what the style draws, quantised to 2048 units per tile,
    deflated. About a fifth of the size, half the render time. Converted in idle time.
  • One I/O task for all SD card access, tiles on screen first. Two render lanes, a PSRAM cache of 32 rendered
    tiles, and prefetch only after a second without input.
  • Cold first z14 screen in dense central Budapest: 1.8-2.0 s on the T-Deck (5.6 s in the first device build).

Known limits

  • At most 3,000 tiles or 50 MB per download. No labels yet. Touch tested on the T-Deck only. No automatic
    region updates yet.

Offline vector maps from OpenFreeMap. Download an area once over Wi-Fi and use it offline from the SD card.
First version (0.0.1), tested on the LilyGO T-Deck only.
@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 132 files, which is 32 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8fa5fd2b-46c7-437a-940b-a9df11e42e29
Commits

Reviewing files that changed from the base of the PR and between 5cf76a5 and bf61a4b.

Files ignored due to path filters: 2
  • Apps/Maps/assets/fonts/LabelSans.ttf is excluded by !**/*.ttf
  • Apps/Maps/assets/regions.bin is excluded by !**/*.bin
Files selected for processing: 132
  • Apps/Maps/CHANGELOG.md
  • Apps/Maps/CMakeLists.txt
  • Apps/Maps/LICENSE
  • Apps/Maps/README.md
  • Apps/Maps/THIRD_PARTY.md
  • Apps/Maps/main/CMakeLists.txt
  • Apps/Maps/main/Source/app/BinFontText.cpp
  • Apps/Maps/main/Source/app/BinFontText.h
  • Apps/Maps/main/Source/app/Debug.cpp
  • Apps/Maps/main/Source/app/Debug.h
  • Apps/Maps/main/Source/app/DisplayOutput.cpp
  • Apps/Maps/main/Source/app/DisplayOutput.h
  • Apps/Maps/main/Source/app/Downloader.cpp
  • Apps/Maps/main/Source/app/Downloader.h
  • Apps/Maps/main/Source/app/GpxImport.cpp
  • Apps/Maps/main/Source/app/GpxImport.h
  • Apps/Maps/main/Source/app/Input.cpp
  • Apps/Maps/main/Source/app/Input.h
  • Apps/Maps/main/Source/app/IoTask.cpp
  • Apps/Maps/main/Source/app/IoTask.h
  • Apps/Maps/main/Source/app/LabelFontMaker.cpp
  • Apps/Maps/main/Source/app/LabelFontMaker.h
  • Apps/Maps/main/Source/app/LabelStore.cpp
  • Apps/Maps/main/Source/app/LabelStore.h
  • Apps/Maps/main/Source/app/LocationSource.cpp
  • Apps/Maps/main/Source/app/LocationSource.h
  • Apps/Maps/main/Source/app/MapApp.cpp
  • Apps/Maps/main/Source/app/MapInput.cpp
  • Apps/Maps/main/Source/app/MapPlaces.cpp
  • Apps/Maps/main/Source/app/MapScreens.cpp
  • Apps/Maps/main/Source/app/MapView.cpp
  • Apps/Maps/main/Source/app/MapView.h
  • Apps/Maps/main/Source/app/PlatformTactility.cpp
  • Apps/Maps/main/Source/app/RootCertificates.h
  • Apps/Maps/main/Source/app/StallProbe.cpp
  • Apps/Maps/main/Source/app/StallProbe.h
  • Apps/Maps/main/Source/app/TileWorker.cpp
  • Apps/Maps/main/Source/app/TileWorker.h
  • Apps/Maps/main/Source/app/TrackRecorder.cpp
  • Apps/Maps/main/Source/app/TrackRecorder.h
  • Apps/Maps/main/Source/gpx/GpxReader.cpp
  • Apps/Maps/main/Source/gpx/GpxReader.h
  • Apps/Maps/main/Source/gpx/GpxWriter.cpp
  • Apps/Maps/main/Source/gpx/GpxWriter.h
  • Apps/Maps/main/Source/gpx/Track.cpp
  • Apps/Maps/main/Source/gpx/Track.h
  • Apps/Maps/main/Source/mvt/ByteInput.cpp
  • Apps/Maps/main/Source/mvt/ByteInput.h
  • Apps/Maps/main/Source/mvt/InflateReader.cpp
  • Apps/Maps/main/Source/mvt/InflateReader.h
  • Apps/Maps/main/Source/mvt/MvtDecoder.cpp
  • Apps/Maps/main/Source/mvt/MvtDecoder.h
  • Apps/Maps/main/Source/mvt/PlaceFinder.cpp
  • Apps/Maps/main/Source/mvt/PlaceFinder.h
  • Apps/Maps/main/Source/mvt/Schema.cpp
  • Apps/Maps/main/Source/mvt/Schema.h
  • Apps/Maps/main/Source/platform/Platform.h
  • Apps/Maps/main/Source/platform/Position.h
  • Apps/Maps/main/Source/render/CacheIndex.cpp
  • Apps/Maps/main/Source/render/CacheIndex.h
  • Apps/Maps/main/Source/render/CacheWalker.cpp
  • Apps/Maps/main/Source/render/CacheWalker.h
  • Apps/Maps/main/Source/render/Compose.cpp
  • Apps/Maps/main/Source/render/Compose.h
  • Apps/Maps/main/Source/render/LabelLayout.cpp
  • Apps/Maps/main/Source/render/LabelLayout.h
  • Apps/Maps/main/Source/render/PreparedLabels.cpp
  • Apps/Maps/main/Source/render/PreparedLabels.h
  • Apps/Maps/main/Source/render/PreparedScanner.cpp
  • Apps/Maps/main/Source/render/PreparedScanner.h
  • Apps/Maps/main/Source/render/PreparedTile.cpp
  • Apps/Maps/main/Source/render/PreparedTile.h
  • Apps/Maps/main/Source/render/Raster.cpp
  • Apps/Maps/main/Source/render/Raster.h
  • Apps/Maps/main/Source/render/Style.cpp
  • Apps/Maps/main/Source/render/Style.h
  • Apps/Maps/main/Source/render/TileCache.cpp
  • Apps/Maps/main/Source/render/TileCache.h
  • Apps/Maps/main/Source/render/TileCodec.cpp
  • Apps/Maps/main/Source/render/TileCodec.h
  • Apps/Maps/main/Source/render/TileRenderer.cpp
  • Apps/Maps/main/Source/render/TileRenderer.h
  • Apps/Maps/main/Source/tiles/ChunkedBody.cpp
  • Apps/Maps/main/Source/tiles/ChunkedBody.h
  • Apps/Maps/main/Source/tiles/DownloadPlan.cpp
  • Apps/Maps/main/Source/tiles/DownloadPlan.h
  • Apps/Maps/main/Source/tiles/IoQueue.h
  • Apps/Maps/main/Source/tiles/RegionCatalog.cpp
  • Apps/Maps/main/Source/tiles/RegionCatalog.h
  • Apps/Maps/main/Source/tiles/RegionMeta.cpp
  • Apps/Maps/main/Source/tiles/RegionMeta.h
  • Apps/Maps/main/Source/tiles/TileJson.cpp
  • Apps/Maps/main/Source/tiles/TileJson.h
  • Apps/Maps/main/Source/tiles/TileMath.cpp
  • Apps/Maps/main/Source/tiles/TileMath.h
  • Apps/Maps/main/Source/tiles/TilePaths.cpp
  • Apps/Maps/main/Source/tiles/TilePaths.h
  • Apps/Maps/main/Source/tiles/TileStore.cpp
  • Apps/Maps/main/Source/tiles/TileStore.h
  • Apps/Maps/main/Source/ui/Canvas.cpp
  • Apps/Maps/main/Source/ui/Canvas.h
  • Apps/Maps/main/Source/ui/Controls.cpp
  • Apps/Maps/main/Source/ui/Controls.h
  • Apps/Maps/main/Source/ui/DownloadFrame.cpp
  • Apps/Maps/main/Source/ui/DownloadFrame.h
  • Apps/Maps/main/Source/ui/DownloadProgress.cpp
  • Apps/Maps/main/Source/ui/DownloadProgress.h
  • Apps/Maps/main/Source/ui/Gestures.cpp
  • Apps/Maps/main/Source/ui/Gestures.h
  • Apps/Maps/main/Source/ui/GpxLayer.cpp
  • Apps/Maps/main/Source/ui/GpxLayer.h
  • Apps/Maps/main/Source/ui/HeadingFilter.cpp
  • Apps/Maps/main/Source/ui/HeadingFilter.h
  • Apps/Maps/main/Source/ui/MapOverlay.cpp
  • Apps/Maps/main/Source/ui/MapOverlay.h
  • Apps/Maps/main/Source/ui/Pins.cpp
  • Apps/Maps/main/Source/ui/Pins.h
  • Apps/Maps/main/Source/ui/PointerAcceleration.cpp
  • Apps/Maps/main/Source/ui/PointerAcceleration.h
  • Apps/Maps/main/Source/ui/PositionTracker.cpp
  • Apps/Maps/main/Source/ui/PositionTracker.h
  • Apps/Maps/main/Source/ui/Settings.cpp
  • Apps/Maps/main/Source/ui/Settings.h
  • Apps/Maps/main/Source/ui/WaypointSheet.cpp
  • Apps/Maps/main/Source/ui/WaypointSheet.h
  • Apps/Maps/main/Source/ui/Waypoints.cpp
  • Apps/Maps/main/Source/ui/Waypoints.h
  • Apps/Maps/main/Source/ui/Widgets.cpp
  • Apps/Maps/main/Source/ui/Widgets.h
  • Apps/Maps/manifest.properties
  • Apps/Maps/tools/build_catalog.py
  • Apps/Maps/tools/make_label_font.py

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Comment thread Apps/Maps/docs/images/about.png Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you remove the images from the docs? It's too hard for me to maintain for now.
I hope to auto-generate them in the future with a simulator in GitHub Actions.

Comment thread Apps/Maps/docs/MEASUREMENTS.md Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

measurements.md and plan.md can be removed

#include "../platform/Platform.h"
#include "../render/Raster.h"

#include <binfont/binfont.h>

@KenVanHoeylandt KenVanHoeylandt Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use binfont-module from the SDK?

app.0.name=Maps
app.0.binary=maps
name=Maps
description=Offline vector maps from OpenFreeMap

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know if the app works without PSRAM?
If not, you can set requires.ram=2M or something similar for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants