Pin clustering приземляется в .NET MAUI 11 Maps
.NET MAUI 11 Preview 3 добавляет встроенный pin clustering в контрол Map на Android и iOS, с группами ClusteringIdentifier и событием ClusterClicked.
Если вы когда-нибудь высыпали несколько сотен pin на Map в .NET MAUI, знаете, что происходит на уровне зума 6: клякса перекрывающихся маркеров, в которые никто не может попасть тапом. Экосистема community-плагинов закрывала эту дыру годами, но притащить стороннюю maps-библиотеку только ради clustering всегда ощущалось тяжеловесно. .NET MAUI 11 Preview 3 чинит это first-party реализацией clustering, запечённой в Microsoft.Maui.Controls.Maps.
Включение
Clustering - opt-in через один boolean на контроле Map. Переключите IsClusteringEnabled, и существующая коллекция Pins автоматически группируется в cluster-маркеры по мере отдаления:
<maps:Map x:Name="StoresMap"
IsClusteringEnabled="True"
MapType="Street" />
На Android лежащая в основе реализация использует кастомный grid-based алгоритм, пересчитывающий cluster-корзины на каждом изменении камеры. На iOS и Mac Catalyst он отдаёт работу нативному MKClusterAnnotation из MapKit, так что поведение clustering совпадает с тем, что пользователи уже видят в Apple Maps и Find My. Windows пока не поддерживается, что совпадает с матрицей платформ контрола Map в целом.
Разделение типов pin с ClusteringIdentifier
Реальные приложения редко хотят все pins в одной корзине. Приложению доставки нужно группировать склады отдельно от точек выдачи, а travel-приложение хочет, чтобы отели и рестораны оставались разными, когда они перекрываются. Property ClusteringIdentifier на Pin контролирует, какие pins группируются вместе: pins, делящие identifier, получают одну корзину, pins с другим identifier формируют независимую.
foreach (var store in cafes)
{
StoresMap.Pins.Add(new Pin
{
Label = store.Name,
Location = new Location(store.Lat, store.Lng),
ClusteringIdentifier = "cafe"
});
}
foreach (var charger in chargingStations)
{
StoresMap.Pins.Add(new Pin
{
Label = charger.Name,
Location = new Location(charger.Lat, charger.Lng),
ClusteringIdentifier = "charger"
});
}
С этим на месте, плотный city-view будет рендерить два cluster-маркера в одном и том же месте вместо схлопывания несвязанных pins в один count.
Реакция на тап по cluster
Стандартное поведение тапа - сделать zoom in на cluster, что обычно именно то, что вы хотите. Если нужно что-то побогаче, вроде показа sheet с ближайшими результатами или загрузки детальных данных, подпишитесь на ClusterClicked. Event arguments дают вам полный список pins, географический центр cluster и флаг Handled, подавляющий стандартный zoom:
StoresMap.ClusterClicked += async (sender, e) =>
{
var names = string.Join(", ", e.Pins.Select(p => p.Label));
await Shell.Current.DisplayAlert(
$"{e.Pins.Count} places nearby",
names,
"OK");
e.Handled = true;
};
Установка e.Handled = true - то, что позволяет оставить камеру на месте и представить кастомный UI вместо этого.
Почему это апгрейд, которого вы ждали
До Preview 3 прагматичными вариантами были написать алгоритм clustering вручную поверх CameraChanged или заменить контрол Map на platform-specific wrapper вроде MPowerKit.GoogleMaps. У обоих были минусы: первый боролся с собственным coordinate snapping MapKit, а второй полностью обходил Microsoft.Maui.Controls.Maps. Иметь IsClusteringEnabled, ClusteringIdentifier и ClusterClicked в коробке значит, что можно сохранить существующие bindings и data templates, добавить одну property и катить.
Фича - часть более широкого epic Maps Control Improvements для .NET 11, так что ожидайте больше полировки вокруг styling и interaction до GA позже в этом году. Пока установите .NET 11 Preview 3 SDK, обновите свой MAUI workload и пусть платформа разбирается с нагромождением.