Skip to content

Maps 0.8.1: tile menu, long routes, GPX following - #73

Open
AdaSzi wants to merge 2 commits into
TactilityProject:mainfrom
AdaSzi:maps-0.6.0
Open

AdaSzi wants to merge 2 commits into
TactilityProject:mainfrom
AdaSzi:maps-0.6.0

Conversation

@AdaSzi

@AdaSzi AdaSzi commented Nov 15, 2026 •

Copy link
Copy Markdown
Contributor

Updates Maps to 0.8.1.

  • Main menu as a grid of nine tiles with icons; a strip shows a running download or route. Sheets, lists and cards lay themselves out for the screen size and orientation
  • Long routes: near the start and destination the search uses the detailed map, between them the main roads of the zoom 10 tiles, so a route only needs detail around both ends
  • Off the route: a new route is asked for at once and found by a short search back to the old route; a log of the navigation is written to navlog.txt in the app's folder
  • Follow a GPX track or route (Menu → GPX → a file → Follow this track)
  • A map that was just downloaded can be routed on at once
  • Labels are fixed to the map and no longer jump; POIs take only the room of their icon, so more show at high zoom. Cities appear by importance when zoomed out and placing labels is faster
  • Search: exact names ordered by the kind of place; streets that only have an international name are found
  • Fixes: track, settings and region files are replaced safely, damaged map files end with an error instead of hanging, weak GPS no longer causes repeated rerouting

Summary by CodeRabbit

  • New Features

    • Added a tile-based main menu with quick access to downloads, search, waypoints, tracks, location, and route controls.
    • Added GPX track following, improved off-route route rejoining, and expanded long-distance routing.
    • Improved map labels, place names, search ordering, and map display performance.
    • Updated controls and screens to adapt to different screen sizes and input methods, with clearer download progress and status.
  • Bug Fixes

    • Improved reliability when saving files, recording tracks, downloading maps, and loading map data.
    • Fixed issues affecting navigation, search, GPX handling, and map data.
  • Documentation

    • Updated the Maps guide and added version 0.8.1 release notes.

- Main menu as a grid of tiles with icons; layouts follow the screen size and orientation
- Routes between towns over the main roads of the zoomed-out map; detailed map only needed near both ends
- Off the route: a new route at once, found by a short search back to the old route
- Follow a GPX track or route
- Labels stay in place while the map moves; more POIs at high zoom; cities by importance when zoomed out
- Search: exact names ordered by kind of place; streets with only an international name
- Safer file writes, damaged map files end with an error, fixes from review
@coderabbitai

coderabbitai Bot commented Nov 15, 2026 •

Copy link
Copy Markdown

Explain Change →

Walkthrough

Maps 0.8.1 adds a tile-grid menu, GPX-track following, route rejoining, and routing across local and coarse graph layers. It also changes search-index construction, label placement, map controls, and UI layouts. File recovery and replacement, GPX and map-tile parsing, download handling, and background-task coordination are updated. The README and changelog describe the revised features and menu paths, and the app package version changes from 0.8.0 to 0.8.1.

Merge Risk: Moderate · up to 30ab2

The release mostly works, but long-distance layered routing can miss a layer link near a bridge, and an interrupted GPX import can overwrite the saved route. A few smaller UI and debug-tool glitches remain. Address the routing and GPX points before merging.

Pre-merge checks: Passed 4

Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title "Maps 0.8.1: tile menu, long routes, GPX following" matches the main changes in this changeset. The diff adds a nine-tile main menu (TileGrid, MenuIcons), long-distance routing across detail…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title "Maps 0.8.1: tile menu, long routes, GPX following" matches the main changes in this changeset. The diff adds a nine-tile main menu (TileGrid, MenuIcons), long-distance routing across detailed and main-road layers (RouteSearch, Navigator rejoin), and GPX track following (followGpxFile, requestTrack, TrackRouteBuilder). It also bumps the version to 0.8.1 in manifest.properties and CHANGELOG.md. The title is specific and omits many other changes, such as label placement, search ordering, and fixes, which is expected for a short title.

Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

Minor · Do not close the menu after a backward page swipe. · MapInput.cpp:786-788

Apps/Maps/main/Source/app/MapInput.cpp:786-788
Minor · Functional Correctness · Quick win

Do not close the menu after a backward page swipe.

A rightward paging drag also emits SwipeBack on release after 60 pixels. This handler then calls goBack() and closes the main menu. Prevent a gesture used for grid paging from also triggering Back. This failure remains after adding the missing redraw. (raw.githubusercontent.com)

Review info

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: 23afd526-0f30-4bd9-8c4b-e1cafd91e5c3
Commits

Reviewing files that changed from the base of the PR and between 5b03a54 and 6cd1494.

Files selected for processing: 96

  • Apps/Maps/CHANGELOG.md
  • Apps/Maps/README.md
  • 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/Debug.cpp
  • Apps/Maps/main/Source/app/Debug.h
  • Apps/Maps/main/Source/app/Downloader.cpp
  • Apps/Maps/main/Source/app/Downloader.h
  • Apps/Maps/main/Source/app/IoTask.cpp
  • Apps/Maps/main/Source/app/MapCamera.cpp
  • Apps/Maps/main/Source/app/MapDownloads.cpp
  • Apps/Maps/main/Source/app/MapInput.cpp
  • Apps/Maps/main/Source/app/MapLabels.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/MapSearch.cpp
  • Apps/Maps/main/Source/app/MapView.cpp
  • Apps/Maps/main/Source/app/MapView.h
  • Apps/Maps/main/Source/app/MapViewInternal.h
  • Apps/Maps/main/Source/app/Navigator.cpp
  • Apps/Maps/main/Source/app/Navigator.h
  • Apps/Maps/main/Source/app/SearchIndexJob.cpp
  • Apps/Maps/main/Source/app/SearchIndexJob.h
  • Apps/Maps/main/Source/app/SearchTask.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/gpx/GpxReader.cpp
  • Apps/Maps/main/Source/gpx/GpxWriter.cpp
  • 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/Schema.h
  • Apps/Maps/main/Source/platform/SafeFile.h
  • Apps/Maps/main/Source/render/CacheIndex.cpp
  • Apps/Maps/main/Source/render/Compose.cpp
  • Apps/Maps/main/Source/render/LabelLayout.cpp
  • Apps/Maps/main/Source/render/LabelLayout.h
  • Apps/Maps/main/Source/render/PreparedTile.cpp
  • Apps/Maps/main/Source/render/PreparedTile.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/RouteSearch.cpp
  • Apps/Maps/main/Source/route/RouteSearch.h
  • Apps/Maps/main/Source/route/RouteSides.cpp
  • Apps/Maps/main/Source/route/RouteSplice.cpp
  • Apps/Maps/main/Source/route/RouteSplice.h
  • Apps/Maps/main/Source/route/RouteTrack.cpp
  • Apps/Maps/main/Source/route/RouteTrack.h
  • Apps/Maps/main/Source/search/FoldTable.h
  • Apps/Maps/main/Source/search/QueryParse.cpp
  • Apps/Maps/main/Source/search/SearchCommon.cpp
  • Apps/Maps/main/Source/search/SearchCommon.h
  • Apps/Maps/main/Source/search/SearchEngine.cpp
  • Apps/Maps/main/Source/search/SearchExtract.cpp
  • Apps/Maps/main/Source/search/SearchIndex.cpp
  • Apps/Maps/main/Source/search/SearchRegion.cpp
  • Apps/Maps/main/Source/search/SearchRegion.h
  • Apps/Maps/main/Source/search/SearchStream.cpp
  • Apps/Maps/main/Source/search/SearchStream.h
  • Apps/Maps/main/Source/search/TextFold.cpp
  • Apps/Maps/main/Source/tiles/BrowsedMove.cpp
  • Apps/Maps/main/Source/tiles/RegionMeta.cpp
  • Apps/Maps/main/Source/tiles/TileStore.cpp
  • Apps/Maps/main/Source/ui/BackgroundWork.cpp
  • Apps/Maps/main/Source/ui/BackgroundWork.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/DownloadProgress.cpp
  • Apps/Maps/main/Source/ui/DownloadProgress.h
  • Apps/Maps/main/Source/ui/MapOverlay.cpp
  • Apps/Maps/main/Source/ui/MapOverlay.h
  • Apps/Maps/main/Source/ui/MenuIcons.cpp
  • Apps/Maps/main/Source/ui/MenuIcons.h
  • Apps/Maps/main/Source/ui/Metrics.h
  • Apps/Maps/main/Source/ui/NavigationBar.cpp
  • Apps/Maps/main/Source/ui/NavigationBar.h
  • Apps/Maps/main/Source/ui/RouteCard.cpp
  • Apps/Maps/main/Source/ui/RouteCard.h
  • Apps/Maps/main/Source/ui/SearchSheet.cpp
  • Apps/Maps/main/Source/ui/SearchSheet.h
  • Apps/Maps/main/Source/ui/Settings.cpp
  • Apps/Maps/main/Source/ui/TileGrid.cpp
  • Apps/Maps/main/Source/ui/TileGrid.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/Widgets.cpp
  • Apps/Maps/main/Source/ui/Widgets.h
  • Apps/Maps/manifest.properties
Files with no reviewable changes: 2

  • Apps/Maps/main/Source/search/SearchRegion.cpp
  • Apps/Maps/main/Source/search/SearchRegion.h

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread Apps/Maps/CHANGELOG.md Resolved
if (strcmp(command, "followgpx") == 0) {
// "followgpx <file>": follows the track of a GPX file in the app's folder, as Menu, GPX, Follow this track
step.action = Action::FollowGpx;
snprintf(step.name, sizeof(step.name), "%s", argument);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor · Functional Correctness · Quick win

Widen Step::name so followgpx keeps the full filename.

followgpx copies its argument into Step::name, which holds only 31 characters. Longer filenames are cut before MapView builds the path. For example, 2026-11-15_Morning_Mountain_Hike.gpx becomes 2026-11-15_Morning_Mountain_Hik. The track cannot be opened under that name. The argument buffer already holds up to 47 characters, so widen name to match. The existing %31s scans still stay within the new size.

Suggested fix

-        char name[32];
+        char name[48];

Comment thread Apps/Maps/main/Source/app/MapInput.cpp Resolved
Comment on lines +171 to +173
routeCard.open("", nullptr);
routeCard.setMode(static_cast<int>(RouteProfile::Walk));
routeCard.setBusy(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Major · Functional Correctness · Quick win

Changing the mode while a GPX track loads routes to the previous destination.

A mode change during the track request cancels that request. The restart then calls searchRouteAgain() before fillRouteCard() sets the track end as the goal. The new route therefore runs from the current position to the old goalLat/goalLon, which is 0.0, 0.0 if no destination was set before. The loaded track is discarded.

Keep the track request running when it is a GPX load. Store the chosen mode, then let fillRouteCard() apply the track's own profile and destination. A flag set in followGpxFile and cleared when the track result is handled can control this in chooseRouteMode.

const int hintY = frame.height - lineHeight - 2;
canvas.fillRect(0, hintY - 2, frame.width, 1, theme.border);
drawClippedText(canvas, font, PADDING, hintY, "Roll: choose Enter: open Esc: close", frame.width - 2 * PADDING, theme.muted);
drawClippedText(canvas, font, PADDING, hintY, trackball ? "Roll: choose Enter: open Esc: close" : "Tab: list Enter: open Esc: close", frame.width - 2 * PADDING, theme.muted);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor · Functional Correctness · Quick win

Show “Tab: list” when no trackball is available.

On a keyboard-only board, MapSearch.cpp sets trackball to true through input.hasKeyboard() || input.hasPointer(). This branch then shows “Roll: choose” for hardware that is not present. Set the flag from actual trackball availability so keyboard-only users see the available Tab action.

const int hintHeight = metrics::hasKeyboard() && !compact ? line : 0;
const int sheetHeight = PADDING + (line + 8) + 4 + (compact ? 0 : textLines() * line) + (compact ? 0 : PADDING) + hintHeight + buttonHeight + PADDING;
// On a very low screen the sheet is cut at the top rather than the screen
frame = { (width - sheetWidth) / 2, std::max(0, height - sheetHeight - 4), sheetWidth, sheetHeight };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor · Functional Correctness · Quick win

Keep the action row inside the available height.

With touch targets, a 16-pixel font and compact layout height of 64 pixels, sheetHeight is 80 pixels. Clamping frame.y to zero places the buttons at y=36..72. The last eight pixels extend into the keyboard area.

Keep the action row bottom-aligned when the sheet exceeds the available height. Allow the upper content to clip, or reduce its height.

- followgpx keeps file names up to 47 characters
- a finished download resumes the next region also after Connecting
- a page swipe in the menu redraws the screen
- a mode chosen while a GPX track loads waits for the track, then routes to its end
- a road route from a GPX card without a GPS fix starts at the track's start
- the navigation log keeps its lines after a failed write, for a few tries
- a rejoin found while the route was cleared is dropped
- a GPX import that is stopped, damaged or unfinished installs nothing
- a failed file replace keeps the temporary file for recovery
- a degenerate map frame (scale 0) shows the placeholder, no fixed-point overflow
- labels on a turned map keep their identity while panning
- the graph builder stops after a failed allocation
- links between the two road levels only join roads on the same level
- coarse and local route steps are not merged at a rejoin
- the download card is laid out again when the keyboard comes or goes
- the search hint says "Roll" only on boards with a trackball
- the compact waypoint sheet keeps its buttons above the on-screen keyboard
- changelog heading 0.8.1

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Review info

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: f9016fe0-59f5-452f-a092-468c78455989
Commits

Reviewing files that changed from the base of the PR and between 6cd1494 and 30ab237.

Files selected for processing: 24

  • Apps/Maps/CHANGELOG.md
  • Apps/Maps/main/Source/app/Debug.cpp
  • Apps/Maps/main/Source/app/Debug.h
  • Apps/Maps/main/Source/app/Input.cpp
  • Apps/Maps/main/Source/app/Input.h
  • Apps/Maps/main/Source/app/MapDownloads.cpp
  • Apps/Maps/main/Source/app/MapInput.cpp
  • Apps/Maps/main/Source/app/MapLabels.cpp
  • Apps/Maps/main/Source/app/MapNavigation.cpp
  • Apps/Maps/main/Source/app/MapSearch.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/render/Compose.cpp
  • Apps/Maps/main/Source/render/LabelLayout.cpp
  • Apps/Maps/main/Source/render/LabelLayout.h
  • Apps/Maps/main/Source/route/RouteGraph.cpp
  • Apps/Maps/main/Source/route/RouteSearch.cpp
  • Apps/Maps/main/Source/route/RouteSearch.h
  • Apps/Maps/main/Source/route/RouteSplice.cpp
  • Apps/Maps/main/Source/tiles/RegionMeta.cpp
  • Apps/Maps/main/Source/ui/Settings.cpp
  • Apps/Maps/main/Source/ui/WaypointSheet.cpp
Files skipped from review as they are similar to previous changes: 1

  • Apps/Maps/CHANGELOG.md

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

};
char word[16] = "";
if (strcmp(command, "routebench") == 0 && sscanf(argument, "%31s %15s", step.name, word) >= 1) {
if (strcmp(command, "routebench") == 0 && sscanf(argument, "%47s %15s", step.name, word) >= 1) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor · Functional Correctness · Quick win

Preserve the profile when accepting longer filenames.

For routebench 1234567890123456789012345678901234567890.txt car, the 44-character filename now fits step.name. However, Line 100 limits the entire argument to 47 characters. The profile becomes ca, so profileOf selects walking instead of car routing.

Increase the argument buffer and its scan limit to hold the filename and trailing parameters. Keep each destination scan bounded.

const float localX = static_cast<float>(at.x) / units - static_cast<float>(key.x) * GRAPH_EXTENT;
const float localY = static_cast<float>(at.y) / units - static_cast<float>(key.y) * GRAPH_EXTENT;
for (uint32_t i = 0; i < tile.nodeCount; i++) {
if (tile.mainNode[i] == 0 || tile.level[i] != level) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Major · Data Integrity & Integration · Heavy lift

Bridge-end nodes take their level from input order, so layer links can be dropped.

GraphBuilder::makeNodes merges vertices at a way end even when their levels differ. The merged node takes the level of the lowest-index vertex in the group, which follows the input order. The detailed graph and the main-road graph are built from separate inputs, so the same bridge-end junction can get a ground level in one graph and a bridge level in the other. When the detailed node has the ground level, linkAcross skips it at RouteSearch.cpp:554 because the search asks for the bridge level. No link is emitted for that junction, and a layered route can lose its descent near the goal if no other same-level node is within LINK_M.

The label should not depend on input order. Define the merged node's level by a rule that both producers apply the same way, and test a route that crosses a bridge end near its destination.

Comment on lines +203 to +204
// A failed replace keeps the temporary file: the old one may be gone already, and recoverFile() needs it
return replaceFile(temporary, path);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor · Data Integrity & Integration · Quick win

Recover the retained metadata before writeRegionMeta() opens path.tmp.

If replaceFile() removes path and its second rename fails, path.tmp holds the only complete metadata. writeRegionMeta() then opens path.tmp with "w" without calling recoverFile(path), which truncates that copy. If the following fwrite or fclose also fails, the error path removes path.tmp, and neither copy remains. Only readRegionMeta() runs recovery, so a write without a prior read does not restore the file.

Call recoverFile(path) before the temporary file is opened. When path exists, the call changes nothing. When path is missing and path.tmp exists, the call renames path.tmp back to path. recoverFile() returns no status, so a failed recovery rename is not detected and the temporary file can still be truncated.

Suggested fix

     snprintf(temporary, sizeof(temporary), "%s.tmp", path);
+    recoverFile(path);
     FILE* file = fopen(temporary, "w");

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.

1 participant