Add Pin Clustering support for Maps#33831
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds pin clustering support to the MAUI Maps control for iOS/MacCatalyst and Android platforms. When enabled, pins that are close together are automatically grouped into cluster markers showing the count of pins. Users can tap clusters to zoom in and see individual pins.
Changes:
- New API properties:
Map.IsClusteringEnabled(bool),Pin.ClusteringIdentifier(string) - New event:
Map.ClusterClickedwithClusterClickedEventArgscontaining the pins and location - iOS/MacCatalyst uses native
MKClusterAnnotationsupport (iOS 11+) - Android implements custom grid-based clustering algorithm with programmatic cluster icons
- Sample gallery page demonstrates clustering with 30-100+ pins
- 5 unit tests added covering property defaults, event handling, and API implementation
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| PublicAPI.Unshipped.txt (all platforms) | Adds new public API entries for clustering properties and methods |
| IMap.cs | Adds IsClusteringEnabled property and ClusterClicked method to interface |
| IMapPin.cs | Adds ClusteringIdentifier property to interface |
| Map.cs | Implements IsClusteringEnabled bindable property and ClusterClicked event |
| Map.Impl.cs | Implements IMap.ClusterClicked to raise event and return handled status |
| Pin.cs | Adds ClusteringIdentifier bindable property with default constant |
| ClusterClickedEventArgs.cs | New event args class containing pins and location with Handled property |
| MauiMKMapView.cs | iOS implementation using MKClusterAnnotation with cluster view creation and zoom behavior |
| MapHandler.iOS.cs | iOS handler mapping for IsClusteringEnabled property |
| MapHandler.Android.cs | Android implementation with custom clustering algorithm, cluster icons, and zoom-based reclustering |
| MapHandler.*.cs (stubs) | NotImplementedException stubs for Windows, Tizen, Standard platforms |
| MapTests.cs | 5 new unit tests for clustering properties, events, and API contracts |
| ClusteringGallery.xaml/.cs | Sample page demonstrating clustering with interactive controls |
| MapsGallery.cs | Navigation button added to access clustering gallery |
|
|
||
| // Recalculate clusters when zoom level changes significantly | ||
| var currentZoom = _googleMap.CameraPosition.Zoom; | ||
| if (Math.Abs(currentZoom - _lastClusterZoom) > 0.5f) |
There was a problem hiding this comment.
The cluster recalculation threshold of 0.5 zoom levels may cause too-frequent reclustering, especially during smooth zoom animations. This could lead to performance issues and visual jitter. Consider increasing the threshold (e.g., 1.0 or 1.5) or implementing a debounce mechanism to avoid recalculating during continuous zoom operations.
| if (Math.Abs(currentZoom - _lastClusterZoom) > 0.5f) | |
| if (Math.Abs(currentZoom - _lastClusterZoom) > 1.0f) |
| else if (OperatingSystem.IsIOSVersionAtLeast(11)) | ||
| { | ||
| // Clear clustering identifier when disabled | ||
| mapPin.ClusteringIdentifier = null; | ||
| } |
There was a problem hiding this comment.
If clustering is disabled via toggling IsClusteringEnabled from true to false, this code clears the ClusteringIdentifier on pin views. However, if clustering is re-enabled later, pins will have lost their original ClusteringIdentifier values. Consider preserving the identifier in the underlying IMapPin data and only conditionally applying it to the view, rather than clearing it.
aad6c21 to
fd027fc
Compare
10f7786 to
497954b
Compare
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
Independent Code ReviewSummary: Adds ✅ What Looks Good
🔴 Issues1. Android implements custom clustering, not Google Maps Utils 2. var controlPins = pins.OfType<Pin>().ToList();This will silently drop any 3. No iOS implementation visible 4. Breaking interface change 🟡 Nits
|
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 33831Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 33831" |
|
Thanks for the review @kubaflo! Addressed the unresolved items and responding to other points: Unresolved thread 1: Cluster recalculation threshold Unresolved thread 2: iOS ClusteringIdentifier preservation 1. Custom clustering vs Google Maps Utils 2. OfType() cast 3. Breaking interface change |
🔍 Round 2 Review — PR #33831 (Pin Clustering)Updated with author response analysis ✅ What Looks Good
|
|
/azp run maui-pr |
|
Azure Pipelines successfully started running 1 pipeline(s). |
📋 Round 3 — PR #33831 (Pin Clustering) — Final RecommendationOne minor suggestion remains (logging when Recommendation: Merge when CI passes. The Merge order note: This PR modifies |
|
/rebase |
- Add IsClusteringEnabled property to Map - Add ClusteringIdentifier property to Pin - Add ClusterClicked event with ClusterClickedEventArgs - Implement iOS clustering using MKClusterAnnotation - Add Android/Windows/Tizen stubs - Add unit tests for clustering functionality - Add ClusteringGallery sample page Fixes #11811
Implements built-in pin clustering for Android platform: - Grid-based clustering algorithm that groups nearby pins - Cluster radius adjusts based on zoom level - Cluster markers with visual count indicator - ClusterClicked event fires when cluster is tapped - Default behavior zooms to show cluster contents - Respects ClusteringIdentifier for grouping Implementation notes: - Uses custom clustering algorithm (no external library dependency) - Creates cluster markers with programmatic blue circle icons - Clusters dynamically adjust when IsClusteringEnabled changes
Switch from gesture recognizers to delegate-based handling for cluster clicks. MapKit intercepts taps before gesture recognizers, so cluster clicks need to be handled in the annotation selection delegate instead. - Remove gesture recognizer attachment for clusters - Handle cluster selection in MkMapViewOnAnnotationViewSelected - Disable callout for cluster markers (don't need them) - Deselect cluster after handling to allow re-selection
- Reduce cluster radius for more reasonable grouping - Add IOnCameraIdleListener to recalculate clusters when zoom changes - Clusters now properly split as user zooms in - Both cluster and individual pin clicks work correctly
- Fix clustering identifier to use Pin.DefaultClusteringIdentifier constant - Fix distance calculation with cos(latitude) adjustment for accuracy near poles - Dispose Paint/Canvas objects to prevent native memory leaks - Add null-safety for VirtualView.ClusterClicked - Clear stale cluster data when clustering is disabled - Document cluster radius constant - Fix iOS duplicate pins when toggling clustering (remove before re-add) - Fix nullable test variable - Await DisplayAlert in gallery sample
- Clear _clusters and _clusterMarkers when clustering is disabled - Use 'using' for Canvas and Paint to prevent native memory leaks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace Pin.DefaultClusteringIdentifier with literal string (handler can't reference Controls) - Remove non-existent RemovePins call (AddPins already clears annotations) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Increase cluster recalculation threshold from 0.5 to 1.0 zoom levels to reduce visual jitter during smooth zoom animations - Clarify iOS ClusteringIdentifier comment: view property is transient, IMapPin data preserves the original value for re-enablement Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1621e29 to
d4ef0e7
Compare
|
🚨 API change(s) detected @davidortinau FYI |
- Add Pin Clustering section to map.md with API reference, XAML/C# examples, and platform notes (iOS/Mac Catalyst via MKClusterAnnotation, Android grid-based, Windows not yet supported) - Update whats-new/dotnet-11.md with Map pin clustering entry and sample badge Upstream PR: dotnet/maui#33831 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Description
Adds Pin Clustering support for the Maps control on iOS/MacCatalyst and Android.
Fixes #11811
Changes
New API
Map.IsClusteringEnabled- Enable/disable pin clusteringPin.ClusteringIdentifier- Group pins into named clustersMap.ClusterClickedevent - Fired when a cluster marker is tappedClusterClickedEventArgs- Contains the list of pins in the clicked clusteriOS/MacCatalyst Implementation
MKClusterAnnotationsupportDidSelectAnnotationViewdelegateAndroid Implementation
Testing
ClusteringGallery)