Skip to content

Updates Maps to 0.5.0 - #72

Merged
KenVanHoeylandt merged 2 commits into
TactilityProject:mainfrom
AdaSzi:maps-0.5.0
Nov 13, 2026
Merged

KenVanHoeylandt merged 2 commits into
TactilityProject:mainfrom
AdaSzi:maps-0.5.0

Conversation

@AdaSzi

@AdaSzi AdaSzi commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Updates Maps to 0.5.0 (includes 0.4.1).

  • Offline search (Menu → Search, / or F): places, streets, addresses ("street number" in either order), points of
    interest and categories, with diacritics and Greek/Cyrillic in Latin letters not mattering
  • Results nearest first with type, distance, direction and the town or city district they are in, nearby by
    category
  • House numbers in the map data have no street: each gets the nearest named street, or the nearest village in
    regions where addresses are "village + number", estimated results are marked
  • The search index is built on the device in idle time, per downloaded region, large regions are split into blocks
  • Off-route detection follows the GPS precision (0.4.1)
  • Tiles on screen get their POIs and house numbers during a download
  • Letters missing from the UI font are drawn as
    their base letter

Summary by CodeRabbit

  • New Features
    • Added offline search for downloaded maps, including place, street, and address queries, nearby categories, and recent searches.
    • Search results can recenter the map and open a place details sheet.
    • Added background search-index building and progress reporting.
    • Map labels now handle more unsupported characters through text fallback.
  • Improvements
    • Route deviation detection now accounts for GPS accuracy, direction, nearby roads, and weak GPS signals.
    • Map downloads better handle urgent tile preparation and tiles that move between regions.
    • Replay testing now supports configurable GPS accuracy and position noise.
  • Documentation
    • Updated Maps version information and documented offline search and navigation behavior.

- Search places, streets, addresses and points of interest in the downloaded maps, offline
- Results nearest first, with type, town, distance and direction; nearby by category; recent searches
- House numbers matched to the nearest street, or to the village where addresses have no street
- Search index built on the device in idle time
- Off-route detection follows the GPS precision
- Tiles on screen get their labels during downloads, letters missing from the UI font drawn as base letters
@coderabbitai

coderabbitai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Explain Change →

Walkthrough

This change adds offline search for downloaded map data in Maps. It introduces text folding, tile extraction, region assembly, index writing and reading, background index building, and a search UI with recents, categories, pagination, and map result opening. It also changes route following to use accuracy-aware off-route rules, nearby side-road data, weak-GPS handling, and tunable recalculation timing. Related updates add debug commands, replay noise controls, urgent tile-conversion handling during downloads, font fallback for unsupported characters, version bumps, and documentation updates.

Severity of issue fixed: Medium

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 "Updates Maps to 0.5.0" names the release that this PR prepares. The version bump is part of the changeset: README, CHANGELOG, manifest.properties, and APP_VERSION all move to 0.5.0. However…
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 "Updates Maps to 0.5.0" names the release that this PR prepares. The version bump is part of the changeset: README, CHANGELOG, manifest.properties, and APP_VERSION all move to 0.5.0. However, the title does not name the main functional changes, which are offline search, the search index build, and off-route detection tuning. The title is partially related: it states a real part of the change but not its main substance.

Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Review coverage is incomplete: 1 file could not be fully reviewed. Findings from completed review steps are included; see review info for details.

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

Caution

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

⚠️ Outside diff range comments (2)

Minor · Long press while the search sheet is open opens a waypoint sheet for the… · MapInput.cpp:690-705

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

Long press while the search sheet is open opens a waypoint sheet for the map behind it

handleLongPress has no searchSheet.isOpen() guard. On Screen::Map, a long press on a search row reaches the controls.hit(...) == Control::None && !sheet.contains(...) branch, because openSearch hid the controls. That branch calls openNewWaypoint for the hidden map point.

The waypoint sheet stays hidden behind the search sheet, because key and tap routing check searchSheet first. After the user closes search, the waypoint sheet appears for an unrelated point.

Return early when the search sheet is open.

Proposed fix

-    if (screen == Screen::Map && !dialog.isOpen()) {
+    if (screen == Screen::Map && !dialog.isOpen() && !searchSheet.isOpen()) {
Minor · Handle the search sheet in goBack(). · MapScreens.cpp:103-109

Apps/Maps/main/Source/app/MapScreens.cpp:103-109
Minor · Functional Correctness · Quick win

Handle the search sheet in goBack().

With Search open, handleGesture() sends a right swipe to goBack(). However, menuOpen is false, screen is Screen::Map, and activeList() returns null. The swipe therefore leaves Search open. Add a search-sheet branch that applies its back action and closes Search when appropriate. Keep menu handling first. (raw.githubusercontent.com)

Nitpick comments: 1

Apps/Maps/main/Source/search/SearchStream.h: 1

118-128: Trivial · Maintainability & Code Quality · Quick win

Have SearchRegionBuilder use houseNumberStyle and houseVoteNumber

The comment on Line 118 says that SearchRegionBuilder uses the same grammar. In fact, SearchRegion.cpp defines its own NumberKind, classifyNumber, leadingNumber and streetNumberOf copies. The two builders must produce the same .sidx file byte for byte. With two copies, a later change to one copy can make the indexes differ. Call these shared functions from SearchRegion.cpp and delete the local copies.

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: d7642a6e-3627-4e72-bafa-2f3985d1558d
Commits

Reviewing files that changed from the base of the PR and between 54fd48c and 99b2d3d.

Files selected for processing: 58

  • Apps/Maps/CHANGELOG.md
  • Apps/Maps/README.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/MapInput.cpp
  • Apps/Maps/main/Source/app/MapNavigation.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/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.cpp
  • 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/mvt/MvtDecoder.cpp
  • Apps/Maps/main/Source/mvt/MvtDecoder.h
  • Apps/Maps/main/Source/mvt/Schema.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/RouteSearch.cpp
  • Apps/Maps/main/Source/route/RouteSearch.h
  • Apps/Maps/main/Source/route/RouteSides.cpp
  • Apps/Maps/main/Source/search/AddressMatch.cpp
  • Apps/Maps/main/Source/search/AddressMatch.h
  • Apps/Maps/main/Source/search/ExternalSort.cpp
  • Apps/Maps/main/Source/search/ExternalSort.h
  • Apps/Maps/main/Source/search/FoldTable.h
  • Apps/Maps/main/Source/search/QueryParse.cpp
  • Apps/Maps/main/Source/search/QueryParse.h
  • Apps/Maps/main/Source/search/ScratchFiles.h
  • Apps/Maps/main/Source/search/SearchEngine.cpp
  • Apps/Maps/main/Source/search/SearchEngine.h
  • Apps/Maps/main/Source/search/SearchExtract.cpp
  • Apps/Maps/main/Source/search/SearchExtract.h
  • Apps/Maps/main/Source/search/SearchIndex.cpp
  • Apps/Maps/main/Source/search/SearchIndex.h
  • 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/search/TextFold.h
  • Apps/Maps/main/Source/ui/BackgroundWork.cpp
  • Apps/Maps/main/Source/ui/BackgroundWork.h
  • Apps/Maps/main/Source/ui/SearchSheet.cpp
  • Apps/Maps/main/Source/ui/SearchSheet.h
  • Apps/Maps/main/Source/ui/WaypointSheet.h
  • Apps/Maps/manifest.properties
  • Apps/Maps/tools/fold.py
  • Apps/Maps/tools/make_fold_table.py
Files not fully reviewed because they exceed a per-file size limit: 2

  • Apps/Maps/main/Source/search/FoldTable.h
  • Apps/Maps/main/Source/search/SearchStream.cpp

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/main/Source/app/MapInput.cpp Resolved
// Also without a new position: standing still off the route still counts its 10 s
if (navigating && now - lastFollowAt >= 1000) {
// No fix for 2 s (a GPS that stopped sending): the last one counts again, so standing still off the route is noticed
if (navigating && static_cast<int32_t>(now - lastFollowAt) >= 2000) {

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

Do not count a cached position as another GPS fix.

After settling, suppose one erroneous position is 20 m from the route with 5 m accuracy. If GPS updates stop, this timer submits the same position every two seconds. The 500 ms guard permits each call. RouteFollower::update increments awayFixes on every call, so the cached position satisfies the three-fix threshold and triggers recalculation without another GPS measurement. See Apps/Maps/main/Source/route/RouteFollower.cpp, Line 348–367, and RouteFollower.h, Line 33–43. (raw.githubusercontent.com)

Track the fix timestamp or generation before advancing the off-route counters. Keep stale-position display updates separate from new-fix evaluation.

Comment thread Apps/Maps/main/Source/app/MapView.cpp Resolved
Comment thread Apps/Maps/main/Source/app/Navigator.cpp Resolved
Comment on lines +1108 to +1112
for (size_t i = 0; i < points.size(); i++) {
if (points[i].settlement != EXTRACT_NOT_SETTLEMENT && points[i].name != EXTRACT_NONE) {
settlements.push({ static_cast<uint32_t>(i), internName(&text[points[i].name]), points[i].settlement });
}
}

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 · Stability & Availability · Quick win

internName copies a settlement name out of text while it appends to text

Line 1110 passes &text[points[i].name] to internName. A settlement name is usually not also a street name, so it is usually new. For a new name, copyText calls text.push(s[i]) in a loop while s still points into text. If a push grows text and moves its buffer, s points to freed memory. The rest of the name then comes from freed memory, and the strcmp checks in later hash lookups can fail. This can corrupt settlement names and pseudo-street names. It also breaks the byte-for-byte match with the streaming builder.

Copy the name before you intern it, or reserve space in copyText before it reads s.

Proposed fix

 uint32_t SearchRegionBuilder::copyText(const char* s) {
     if (s == nullptr) {
         return EXTRACT_NONE;
     }
     const auto offset = static_cast<uint32_t>(text.size());
     const size_t length = strlen(s);
+    // s can point into text: copy it before text can move
+    GrowArray<char> copy;
+    for (size_t i = 0; i <= length; i++) {
+        copy.push(s[i]);
+    }
     for (size_t i = 0; i <= length; i++) {
-        text.push(s[i]);
+        text.push(copy[i]);
     }
     return offset;
 }

@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: 1

Caution

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

⚠️ Outside diff range comments (2)

Major · Return an error when lineHash fails inside addLines. · SearchStream.cpp:841

Apps/Maps/main/Source/search/SearchStream.cpp:841
Major · Stability & Availability · Quick win

Return an error when lineHash fails inside addLines.

takeFeature checks the tables only once per feature. addLines then processes every line in that feature. Lines 873-875 push to lineName and lineHash separately. If the lineHash growth fails while lineName succeeds, lineName gets one more entry than lineHash. The duplicate check on the next line of the same feature then reads lineHash[k] at the new index. That read is past the end of the lineHash allocation. It is undefined behavior and can crash during index building. Under the same condition, the unwritten slot can also hold a stale hash that drops a valid line.

The build already fails on the next feature, so returning the error here does not change the result. It only stops the bad read.

Suggested fix

             const uint32_t nameId = names.intern(name);
             lineName.push(nameId);
             lineHash.push(hash);
+            if (lineName.failed() || lineHash.failed()) {
+                return fail("no memory for the tile tables");
+            }
             rec.clear();
Minor · Process pending buffer cleanup before the download memory check. · TileWorker.cpp:724

Apps/Maps/main/Source/app/TileWorker.cpp:724
Minor · Functional Correctness · Quick win

Process pending buffer cleanup before the download memory check.

If cache slots are already at their minimum, this return prevents lane.trimBuffers.exchange(false) from releasing retained buffers. For example, 1,200 KiB free fails the 1,300 KiB threshold even when pending cleanup could release another 256 KiB. Each retry reaches the same return, so urgent tiles can remain unprepared until the download ends. Move pending buffer cleanup before this check. (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: ae6aa04a-4023-4a1a-abf2-89c75971e8e3
Commits

Reviewing files that changed from the base of the PR and between 99b2d3d and 3acba76.

Files selected for processing: 27

  • Apps/Maps/main/Source/app/BinFontText.cpp
  • Apps/Maps/main/Source/app/MapInput.cpp
  • Apps/Maps/main/Source/app/MapNavigation.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/SearchIndexJob.cpp
  • Apps/Maps/main/Source/app/SearchIndexJob.h
  • Apps/Maps/main/Source/app/TileWorker.cpp
  • 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/route/RouteFollower.cpp
  • Apps/Maps/main/Source/route/RouteFollower.h
  • Apps/Maps/main/Source/route/RouteSearch.h
  • Apps/Maps/main/Source/route/RouteSides.cpp
  • Apps/Maps/main/Source/search/ExternalSort.cpp
  • Apps/Maps/main/Source/search/QueryParse.cpp
  • Apps/Maps/main/Source/search/SearchEngine.cpp
  • Apps/Maps/main/Source/search/SearchExtract.cpp
  • Apps/Maps/main/Source/search/SearchExtract.h
  • 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/ui/SearchSheet.cpp
Files with no reviewable changes: 1

  • Apps/Maps/main/Source/search/SearchStream.h
Files not fully reviewed because they exceed a per-file size limit: 1

  • Apps/Maps/main/Source/search/SearchStream.cpp

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


// Weak GPS only stops the off-route rule: a fix further than the match limit (accuracy + MATCH_METRES), beyond the
// goal say, moves neither the progress nor makes an arrival
if (rule == OffRouteRule::None && distance <= matchLimit) {

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

Clear the off-route state when weak GPS suppresses detection.

If current.state is OffRoute, a weak fix beyond matchLimit sets rule to None but leaves the state as OffRoute. For example, median accuracy of 80 m and route distance of 150 m take this path.

MapNavigation.cpp, Line 500–510, still treats that state as a recalculation request. After a failed recalculation, it can request another route during weak GPS instead of showing “Weak GPS”.

When weak GPS prevents a valid match, retain progress but set a non-triggering state. Do not leave OffRoute active.

@KenVanHoeylandt
KenVanHoeylandt merged commit 263c11a into TactilityProject:main Nov 13, 2026
39 checks passed
@AdaSzi
AdaSzi deleted the maps-0.5.0 branch November 15, 2026 04:28
@AdaSzi
AdaSzi restored the maps-0.5.0 branch November 15, 2026 04:28
@AdaSzi
AdaSzi deleted the maps-0.5.0 branch November 15, 2026 04:37
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