Skip to content

feat(map): My Location toggle, user-tracking button & distance-based cluster fan-out#2132

Merged
garthvh merged 1 commit into
mainfrom
feat/map-my-location
Jul 20, 2026
Merged

feat(map): My Location toggle, user-tracking button & distance-based cluster fan-out#2132
garthvh merged 1 commit into
mainfrom
feat/map-my-location

Conversation

@garthvh

@garthvh garthvh commented Jul 20, 2026

Copy link
Copy Markdown
Member

What & why

Recovers uncommitted follow-up work to the MKMapView map (#1989) that was sitting only in a local working tree on a now-stale branch, and rebases it cleanly onto current main.

My Location control

  • New "My Location" toggle in Map settings (persisted via enableMapUserLocation).
  • MeshMapMK binds showUserLocation to that setting and gates the blue dot on isMapVisible, so the user-location dot turns off while the Map tab is off-screen or the app is backgrounded.
  • ClusterMapView adds a native MKUserTrackingButton (follow / center-on-me), styled to match the existing pitch/compass controls and hidden when the dot is off so it isn't a dead button.

Cluster fan-out fix

  • ClusterMapView: floor the cluster zoom span (~140 m) so a tight "2" cluster still zooms to a readable street level instead of an imperceptible nudge.
  • MeshMapMK: replace grid-quantized spread grouping with distance-based union-find (16 m). A fixed grid missed two real cases that produced un-splittable "2" clusters — pairs straddling a cell boundary, and same-site radios whose GPS fixes jitter a few meters apart. Union-find over a coarse neighbor grid keeps it ~O(n) on the density-limited snapshot set.

Notes

  • "Precise Locations Only" (added on main since this work was branched) and the new "My Location" toggle both live in the same settings group — both preserved.
  • Not build-verified locally: the Meshtastic scheme embeds a Watch app requiring the watchOS 26.5 simulator runtime (not installed here), and the repo has no build/test CI. Verified via SwiftLint (clean; the two type_body_length warnings pre-exist on main) + symbol-existence checks against main's current files. A quick on-device pass of the toggle + tracking button + cluster splitting is worth doing.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a persistent My Location option to map settings.
    • Added a native map control for centering and following the device’s location.
    • Map location display now respects the setting and map visibility.
  • Improvements
    • Improved cluster zoom behavior for clearer map views.
    • Refined overlapping node placement to reduce visual clutter and improve readability.

…luster fan-out

Recovers uncommitted follow-up work to the MKMapView map (#1989) and rebases it
onto current main.

- Add a "My Location" toggle (persisted `enableMapUserLocation`) to Map settings.
- MeshMapMK: bind `showUserLocation` to that setting and gate the blue dot on
  `isMapVisible` so it turns off while the tab is off-screen/backgrounded.
- ClusterMapView: add a native MKUserTrackingButton (follow/center), hidden when
  the user-location dot is off; floor the cluster zoom span (~140 m) so a tight
  "2" cluster still breaks apart to a readable street level.
- MeshMapMK: replace grid-quantized spread grouping with distance-based union-find
  (16 m) so stacked pins straddling a grid cell or jittering a few meters apart are
  reliably fanned out into individual tappable circles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The map adds a persisted “My Location” setting, integrates MapKit’s native tracking button, improves tight-cluster zoom behavior, and replaces coordinate quantization with distance-based node grouping and centroid fan-out.

Changes

Map behavior

Layer / File(s) Summary
Persisted My Location setting
Localizable.xcstrings, Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift, Meshtastic/Views/Nodes/MeshMapMK.swift
Adds the localized, persisted “My Location” toggle and gates location display by the setting and map visibility.
Tracking control and cluster zoom
Meshtastic/Views/Nodes/Helpers/Map/ClusterMapView.swift
Adds the native tracking button and applies expanded padding plus a minimum zoom span for tight clusters.
Distance-based node spreading
Meshtastic/Views/Nodes/MeshMapMK.swift
Groups nearby nodes by real distance and fans each group around its centroid with size-scaled offsets.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MapSettingsForm
  participant MeshMapMK
  participant ClusterMapView
  User->>MapSettingsForm: Toggle My Location
  MapSettingsForm->>MeshMapMK: Update persisted setting
  MeshMapMK->>ClusterMapView: Apply location visibility configuration
  ClusterMapView->>User: Show or hide tracking control
Loading

Possibly related PRs

Suggested reviewers: bruschill, rcgv1

Poem

A bunny found a blue dot bright,
And tucked its map choice in for night.
Clusters spread in rings of cheer,
Tight zooms now make the nodes appear.
“My Location!” hops in view—
With native controls, fresh and new.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: My Location controls and distance-based cluster fan-out.
Description check ✅ Passed The description covers what changed, why, and testing notes; only non-critical template sections like screenshots and checklist are omitted.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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 @coderabbitai help to get the list of available commands.

@garthvh
garthvh merged commit e9c54e9 into main Jul 20, 2026
3 of 5 checks passed
@garthvh
garthvh deleted the feat/map-my-location branch July 20, 2026 19:12

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Meshtastic/Views/Nodes/Helpers/Map/ClusterMapView.swift`:
- Around line 504-522: Update gestureRecognizer(_:shouldReceive:) to walk from
the touched view up to the MKMapView and reject touches on either
MKAnnotationView or any UIControl, including the tracking, pitch, and compass
buttons. Preserve accepting touches that reach the map without matching those
excluded view types.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e9940bcd-a6c9-4c07-af2e-b0f33d899b02

📥 Commits

Reviewing files that changed from the base of the PR and between b230bc5 and 3e215bc.

📒 Files selected for processing (4)
  • Localizable.xcstrings
  • Meshtastic/Views/Nodes/Helpers/Map/ClusterMapView.swift
  • Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift
  • Meshtastic/Views/Nodes/MeshMapMK.swift

Comment on lines +504 to +522
// Native follow/center control — MapKit owns the user-location camera behavior. Styled to
// match the pitch button; hidden while the user-location dot is off (see applyConfiguration).
let tracking = MKUserTrackingButton(mapView: mapView)
tracking.tintColor = .label
tracking.backgroundColor = UIColor.tertiarySystemBackground.withAlphaComponent(0.92)
tracking.layer.cornerRadius = 8
tracking.layer.shadowColor = UIColor.black.cgColor
tracking.layer.shadowOpacity = 0.2
tracking.layer.shadowRadius = 2
tracking.layer.shadowOffset = CGSize(width: 0, height: 1)
tracking.translatesAutoresizingMaskIntoConstraints = false
tracking.isHidden = !(appliedConfiguration?.showsUserLocation ?? true)
NSLayoutConstraint.activate([
tracking.widthAnchor.constraint(equalToConstant: 44),
tracking.heightAnchor.constraint(equalToConstant: 44)
])
userTrackingButton = tracking

let stack = UIStackView(arrangedSubviews: [tracking, pitch, compass])

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

New tracking button sits inside the waypoint-creation gesture zone — long-press can create an unwanted waypoint.

gestureRecognizer(_:shouldReceive:) (below, unchanged) only rejects touches on MKAnnotationView; it doesn't reject touches on this new MKUserTrackingButton (or the existing pitch button). Combined with shouldRecognizeSimultaneouslyWith always returning true, a long-press directly on the tracking button will also fire handleMapLongPressonMapLongPressbeginNewWaypoint, creating a waypoint under the button.

🐛 Suggested fix — reject touches on any control, not just annotation views (applies at the `gestureRecognizer(_:shouldReceive:)` implementation, ~line 812)
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    guard let mapView = gestureRecognizer.view as? MKMapView else { return true }
    var hit = mapView.hitTest(touch.location(in: mapView), with: nil)
    while let view = hit, view !== mapView {
        if view is MKAnnotationView || view is UIControl { return false }
        hit = view.superview
    }
    return true
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Meshtastic/Views/Nodes/Helpers/Map/ClusterMapView.swift` around lines 504 -
522, Update gestureRecognizer(_:shouldReceive:) to walk from the touched view up
to the MKMapView and reject touches on either MKAnnotationView or any UIControl,
including the tracking, pitch, and compass buttons. Preserve accepting touches
that reach the map without matching those excluded view types.

@github-actions

Copy link
Copy Markdown

📄 Docs staleness warning

This PR modifies user-facing Swift source files but does not update any page under docs/user/ or docs/developer/.

Changed source files:

Meshtastic/Views/Nodes/Helpers/Map/ClusterMapView.swift
Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift
Meshtastic/Views/Nodes/MeshMapMK.swift

What to check:

Changed area Likely doc page
Views/Messages/ docs/user/messages.md
Views/Nodes/ docs/user/nodes.md
Views/Map/ docs/user/map.md
Views/Settings/Bluetooth/ docs/user/bluetooth.md
Views/Settings/Discovery/ docs/user/discovery.md
Views/Settings/MQTT/ docs/user/mqtt.md
Views/Settings/TAK/ docs/user/tak.md
Views/Settings/Firmware/ docs/user/firmware.md
Views/Settings/ (telemetry/sensor) docs/user/telemetry.md
Views/Settings/ (general) docs/user/settings.md
Meshtastic Watch App/ docs/user/watch.md
Model/ docs/developer/swiftdata.md or docs/developer/architecture.md
Accessory/Transports/ docs/developer/transport.md

If this PR does not require a doc update (e.g., internal refactor, bug fix, test change), add the skip-docs-check label to dismiss this warning.

After updating docs/, re-run bash scripts/build-docs.sh --output Meshtastic/Resources/docs locally and commit the regenerated HTML bundle.

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