Skip to content

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

Merged
KenVanHoeylandt merged 6 commits into
TactilityProject:mainfrom
AdaSzi:maps
Nov 12, 2026
Merged

KenVanHoeylandt merged 6 commits into
TactilityProject:mainfrom
AdaSzi:maps

Conversation

@AdaSzi

@AdaSzi AdaSzi commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Maps 0.4.0 (beta): offline vector maps and navigation for Tactility

An offline map app for Tactility. It downloads OpenStreetMap vector tiles from OpenFreeMap by itself, stores them on the SD card and draws them on the device. It also shows your position, records tracks, imports GPX files and calculates walking, cycling and driving routes, without a connection once the map is downloaded.

This is a beta: At this point, the LilyGO T-Deck (and T-Deck Plus variant) is the only officially supported device. Although M5Stack Tab5 has been reported working. (see Known limits).

Requirements

  • An ESP32-S3 board with 8 MB PSRAM (requires.ram=8M) and an SD card.
  • Wi-Fi for downloading maps. Viewing and navigation work offline.
  • A recent Tactility build (main, November 2026).
  • For your position: a GPS receiver set up in Tactility, or approximate location (Wi-Fi/IP).
  • Tested on the LilyGO T-Deck: keyboard, trackball and touch. Touch-only and button-only operation are supported.

Features

Map

  • Vector maps (OpenMapTiles schema): water, land use, parks, buildings, roads, railways, boundaries, drawn on the device with anti-aliasing. Zoom 0–19. Above zoom 14 the map stays sharp.
  • Labels for places, streets, rivers and peaks, placed so they never overlap. They cover Latin, Greek, Cyrillic and Vietnamese. Names in other scripts show their Latin-script form.
  • Points of interest with icons from zoom 15, and house numbers from zoom 17. Both can be selected.
  • Smooth zoom, map rotation with a compass button, and a clean screen: controls appear only when asked for.
  • High-contrast palettes for monochrome and greyscale displays.

Downloading

  • Choose area: a frame in the middle of the screen, select by moving and zooming the map under it.
  • Choose region: continent → country → region, from a built-in catalog.
  • Before downloading you see the tile count, estimated size and maximum zoom. The zoom is chosen automatically to stay within OpenFreeMap's limits.
  • Downloads run in the background, resume after a restart, and regions share tiles.
  • With Wi-Fi, missing detail is fetched while you browse. Those tiles are reused, not fetched again, when you later download the area.
  • Outdated regions update quietly when the map is left alone, only on Wi-Fi.

Position, waypoints, tracks, GPX

  • Your position from Tactility's location service, with an accuracy circle. Follow mode can keep north up or turn the map to your direction of travel.
  • Waypoints: save, name, list, go to, rename, delete. They're stored as standard GPX.
  • Track recording to GPX: start, pause, resume, stop.
  • GPX import: copy files to /sdcard/gpx. Tracks, routes and waypoints are drawn on the map.

Navigation (offline)

  • Routes for walking, cycling and driving, calculated on the device from the downloaded map data. No server, no extra download.
  • Route preview with distance and time. While navigating: next-turn instructions with street names, a heading-up map, recalculation when you leave the route, and an arrival message.

Quick manual

Controls

Action Keyboard Trackball Touch
Move the map W A S D Roll Drag
Zoom in / out I / O — Pinch, double tap, + / −
Turn the map / back to north Q / E, N — Two-finger twist, compass button
Show / hide controls Space Click Tap the map
My position: follow / heading-up / off G — ⌖ button
Menu M Long click ☰ button
Select a place (POI, house number) Enter (nearest to centre, again for next) Click it Tap it
New point at the map (waypoint, route) P — Hold the map
Download the area on screen Shift + D — Menu → Download map
Back / close Esc or Backspace — < button or swipe back

In lists, Enter or a tap opens the actions for that row (go to, rename, delete…). Key hints are shown only on devices with a keyboard. Boards without one get an on-screen keyboard for names.

Download a map

  1. Menu → Download map → Choose area (frame the area by moving and zooming the map) or Choose region (pick from the list).
  2. Check the tile count, size and zoom, then confirm with Download (Enter, or tap).
  3. Progress shows in a small pill at the top. Tap it for details, pause or cancel. Your downloads are listed under Downloaded maps.

Navigate

  1. Select a place, a waypoint, or hold the map at your destination, then choose Go.
  2. Pick Walk, Bike or Car at the top of the card. The route, its distance and time are shown.
  3. Choose Start. Follow the bar at the top, the map turns with your direction of travel.
  4. To stop, press X on the bar or Esc. Menu → Clear route removes the line.

Without a GPS fix, Go asks where to start: a point you choose on the map, the screen centre, or your approximate position.

Status indicators

  • Amber dot (top left): the app is working in the background, e.g. preparing tiles. Tap it to see what's running.
  • Red dot (top right): a track is being recorded.

Settings

Labels (Off / Few / Normal), Latin names, default route mode, smooth zoom, pointer speed, units and more. Menu → Location switches Tactility's location service on or off.

Known limits (beta)

  • Downloads: at most 3,000 tiles or 50 MB per download (OpenFreeMap's terms). Big countries get a lower maximum zoom.
  • Car routes follow road types only. The map data has no turn restrictions, access rules or speed limits, so check car routes against the road.
  • Long routes: in dense cities, routes longer than roughly 8–10 km can exceed the device's memory and end with "Route too long for this device". A main-road layer for long routes is planned.
  • Footpaths: some footpath junctions are missing from the map data, so walking routes can take small detours.
  • Field testing: GPS navigation, track recording and heading-up have had little outdoor testing so far.
  • Other limits:
    • track recording runs only while the app is open
    • Chinese, Japanese, Korean and Arabic names show in Latin script
    • the 1-bit display style has not been checked on real hardware yet

Data and storage

  • App data: /sdcard/tactility/appdata/tactility.maps.
    • Maps are in maps/<region>/, one file per tile: the original vector tile plus smaller prepared copies for drawing, labels and routing, made on the device.
    • Waypoints are in waypoints.gpx, tracks in tracks/.
  • Rendered images are kept only in memory. Nothing raster is stored on the card.

Credits and licences

  • App: GPL-3.0. Designed by Adam Szilvási.
  • Map data © OpenStreetMap contributors (ODbL), schema © OpenMapTiles, tiles served by OpenFreeMap. The attribution is always shown on the map.
  • Icons: Maki (CC0). Label font: a subset of Adwaita Sans.
  • Parts of the tile decoding are ported from m5-cardputer-offgrid-tiny-map (Apache-2.0). See THIRD_PARTY.md.

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 163 files, which is 63 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: dca248d7-8767-428b-a00d-d8aa736056d8
Commits

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

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: 163

  • 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/BrowsedCounter.cpp
  • Apps/Maps/main/Source/app/BrowsedCounter.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/MapNavigation.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/Navigator.cpp
  • Apps/Maps/main/Source/app/Navigator.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/PoiIconData.h
  • Apps/Maps/main/Source/render/PoiTypes.cpp
  • Apps/Maps/main/Source/render/PoiTypes.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/route/GrowArray.h
  • Apps/Maps/main/Source/route/RouteFollower.cpp
  • Apps/Maps/main/Source/route/RouteFollower.h
  • Apps/Maps/main/Source/route/RouteGraph.cpp
  • Apps/Maps/main/Source/route/RouteGraph.h
  • Apps/Maps/main/Source/route/RouteProfile.h
  • Apps/Maps/main/Source/route/RouteSearch.cpp
  • Apps/Maps/main/Source/route/RouteSearch.h
  • Apps/Maps/main/Source/route/RouteTurn.h
  • Apps/Maps/main/Source/tiles/BrowsedMove.cpp
  • Apps/Maps/main/Source/tiles/BrowsedMove.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/BackgroundWork.cpp
  • Apps/Maps/main/Source/ui/BackgroundWork.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/NavigationBar.cpp
  • Apps/Maps/main/Source/ui/NavigationBar.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/RouteCard.cpp
  • Apps/Maps/main/Source/ui/RouteCard.h
  • Apps/Maps/main/Source/ui/Settings.cpp
  • Apps/Maps/main/Source/ui/Settings.h
  • Apps/Maps/main/Source/ui/TouchKeyboard.cpp
  • Apps/Maps/main/Source/ui/TouchKeyboard.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/label_codepoints.txt
  • Apps/Maps/tools/make_label_font.py
  • Apps/Maps/tools/make_poi_icons.py
  • Apps/Maps/tools/mvt_lite.py
  • Apps/Maps/tools/scan_name_codepoints.py

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

Pre-merge checks: Passed 1 · Inconclusive 1

Failed Failed checks: 1 inconclusive

Check name Status Explanation Resolution
Title check Pending This assessment has not completed. The check will be retried on the next pre-merge check run.
Passed Passed checks: 1 passed

Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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 Resolved
Comment thread Apps/Maps/docs/MEASUREMENTS.md Resolved
Comment thread Apps/Maps/manifest.properties Resolved
Comment thread Apps/Maps/manifest.properties Resolved
- Icons and names for points of interest and house numbers, with a place sheet
- Every function works with only a touch screen (on-screen keyboard for text fields)
- Small dot in the top-left corner while background work runs
- Smooth drawing of a turned map once it stops moving
- Browsed tiles move into a download instead of being fetched again
- Downloads use one connection; tiles are prepared in idle time
- Fix tiles staying coarse when memory is short
- Smaller label font (39 KB instead of 80 KB)
- App data in appdata/ (Tactility main, November 2026)
- Routes for walking, cycling and driving, found on the device from the downloaded maps
- Route preview with walk, bike and car buttons; default mode in Settings
- Turn-by-turn following: next turn and street, distance and time left, off-route recalculation, arrival
- Without a GPS fix, the start is chosen on the map, at the screen centre or at an approximate position
- Prepared tiles split into geometry, label and routing files: maps open and label faster
- Key hints only on boards with a keyboard
- target.platforms: esp32s3 and esp32p4
- README: version 0.4.0, ESP32-P4 in the requirements
@KenVanHoeylandt
KenVanHoeylandt merged commit a6bca74 into TactilityProject:main Nov 12, 2026
45 of 75 checks passed
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