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.ResolveCompleted += (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.
Thanks for feedback
RispondiEliminaQuesto commento è stato eliminato da un amministratore del blog.
RispondiEliminaQuesto commento è stato eliminato dall'autore.
EliminaThanks a lot for your article but I have a problem.
RispondiEliminaI have a lot of pushpins (1200pt on Paris) and when I scroll ou Zoom, the App freeze.
How I can resolve that ?
I have another user who reported me the freeze on zoom. I need to investigate
EliminaThomas try now. I changed the 3 events with ResolveCompleted
EliminaQuesto commento è stato eliminato dall'autore.
RispondiEliminaHi Tiziano! I'm italian like you and your code has been very usefull for me! I've only a little problem. I would like to capture the tap event on the pushpins but I don't know how yo do it. Could you help me?
RispondiEliminaYou can add the tap event in the DataTemplate
EliminaOK! But if I want a different tap handler for each pushpin? How can I do it? Thanks
EliminaHi Tiziano! I am successfully added almost 1000 pushpins in map. But, I need the position of each position of pushpin on tap event. When I create a pushpin I assign pushpin.Tag to a geocordinate. But on tap event I can't get the tag. Could you help me?
RispondiElimina