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.
0 comments:
Post a Comment