Visualizzazione post con etichetta Maps. Mostra tutti i post
Visualizzazione post con etichetta Maps. Mostra tutti i post

sabato 29 giugno 2013

Windows Phone 8 - Map and Clusters

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.

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.