This code example demonstrates how to dynamically group pushpins in the map control.
There is a lot of code for Windows Phone 7, then I merged all what I need to create a project for WP8.
First of all you need some namespace declaration: for map control and for pushpins from WP Toolkit.
You need also two templates: one for a standard pushpin and the other for the cluster.
ClustersGenerator is the core of the project. It's a static class that accepts in input
Every map event launches the pushpins elaboration, but first to explain GeneratePushpins method, let's introduce another class: PushpinGroup.
PushpinGroup represents a standard pushpin or a cluster, and exposes a GetElement method to return them. If the group is a cluster, it needs to get only the first pushpin GeoCoordinate and the content is a group of all pushpins.
The GeneratePushipins function creates clusters based on map ViewPort and a constant named MAXDISTANCE. An extension method convert pushpin GeoCoordinate to a ViewPort Point. That is used to get the distance from other points. If this distance is less then the MAXDISTANCE, the pushpin become a part of cluster.
The extension method GetDistanceTo is the algorithm to calculate the distance between two points:
Instead IsPointVisible returns true if the point is visible in the map, otherwise false:
Now in your MainPage.xaml, you only need to pass all pushpins to the ClusterGenerator and it will do all work for you.
You can download all code here.
With this article I won TechNet Guru Contribution June 2013 - Windows Phone.
There is a lot of code for Windows Phone 7, then I merged all what I need to create a project for WP8.
First of all you need some namespace declaration: for map control and for pushpins from WP Toolkit.
xmlns:map="clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps"
xmlns:maptk="clr-namespace:Microsoft.Phone.Maps.Toolkit;assembly=Microsoft.Phone.Controls.Toolkit"
You need also two templates: one for a standard pushpin and the other for the cluster.
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="PushpinTemplate">
<maptk:Pushpin GeoCoordinate="{Binding GeoCoordinate}" Content="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="ClusterTemplate">
<maptk:Pushpin GeoCoordinate="{Binding GeoCoordinate}" Content="{Binding Count}"/>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
ClustersGenerator is the core of the project. It's a static class that accepts in input
- Map control
- Pushpins collection
- Cluster DataTemplate.
public ClustersGenerator(Map map, List<Pushpin> pushpins, DataTemplate clusterTemplate)
{
_map = map;
_pushpins = pushpins;
this.ClusterTemplate = clusterTemplate;
// maps event
_map.ViewChanged += (s, e) => GeneratePushpins();
_map.ZoomLevelChanged += (s, e) => GeneratePushpins();
_map.CenterChanged += (s, e) => GeneratePushpins();
// first generate
GeneratePushpins();
}
Every map event launches the pushpins elaboration, but first to explain GeneratePushpins method, let's introduce another class: PushpinGroup.
PushpinGroup represents a standard pushpin or a cluster, and exposes a GetElement method to return them. If the group is a cluster, it needs to get only the first pushpin GeoCoordinate and the content is a group of all pushpins.
public class PushpinsGroup
{
private List<Pushpin> _pushpins = new List<Pushpin>();
public Point MapLocation { get; set; }
public PushpinsGroup(Pushpin pushpin, Point location)
{
_pushpins.Add(pushpin);
MapLocation = location;
}
public FrameworkElement GetElement(DataTemplate clusterTemplate)
{
if (_pushpins.Count == 1)
return _pushpins[0];
// more pushpins
return new Pushpin()
{
// just need the first coordinate
GeoCoordinate = _pushpins.First().GeoCoordinate,
Content = _pushpins.Select(p => p.DataContext).ToList(),
ContentTemplate = clusterTemplate,
};
}
public void IncludeGroup(PushpinsGroup group)
{
foreach (var pin in group._pushpins)
_pushpins.Add(pin);
}
}
The GeneratePushipins function creates clusters based on map ViewPort and a constant named MAXDISTANCE. An extension method convert pushpin GeoCoordinate to a ViewPort Point. That is used to get the distance from other points. If this distance is less then the MAXDISTANCE, the pushpin become a part of cluster.
private void GeneratePushpins()
{
List<PushpinsGroup> pushpinsToAdd = new List<PushpinsGroup>();
foreach (var pushpin in _pushpins)
{
bool addGroup = true;
var newGroup = new PushpinsGroup(pushpin, _map.ConvertGeoCoordinateToViewportPoint(pushpin.GeoCoordinate));
foreach (var pushpinToAdd in pushpinsToAdd)
{
double distance = pushpinToAdd.MapLocation.GetDistanceTo(newGroup.MapLocation);
if (distance < MAXDISTANCE)
{
pushpinToAdd.IncludeGroup(newGroup);
addGroup = false;
break;
}
}
if (addGroup)
pushpinsToAdd.Add(newGroup);
}
_map.Dispatcher.BeginInvoke(() =>
{
_map.Layers.Clear();
MapLayer layer = new MapLayer();
foreach (var visibleGroup in pushpinsToAdd.Where(p => _map.IsVisiblePoint(p.MapLocation)))
{
var cluster = visibleGroup.GetElement(this.ClusterTemplate) as Pushpin;
if (cluster != null)
{
layer.Add(new MapOverlay() { GeoCoordinate = cluster.GeoCoordinate, Content = cluster.Content, ContentTemplate = cluster.ContentTemplate});
}
}
if (layer.Count > 0)
_map.Layers.Add(layer);
});
}
The extension method GetDistanceTo is the algorithm to calculate the distance between two points:
public static double GetDistanceTo(this Point p1, Point p2)
{
return Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y));
}
Instead IsPointVisible returns true if the point is visible in the map, otherwise false:
public static bool IsVisiblePoint(this Map map, Point point)
{
return point.X > 0 && point.X < map.ActualWidth && point.Y > 0 && point.Y < map.ActualHeight;
}
Now in your MainPage.xaml, you only need to pass all pushpins to the ClusterGenerator and it will do all work for you.
var clusterer = new ClustersGenerator(map, pushpins, this.Resources["ClusterTemplate"] as DataTemplate);
You can download all code here.
With this article I won TechNet Guru Contribution June 2013 - Windows Phone.




