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"
<
phone:PhoneApplicationPage.Resources
>
DataTemplate
x:Key
=
"PushpinTemplate"
maptk:Pushpin
GeoCoordinate
"{Binding GeoCoordinate}"
Content
"{Binding}"
/>
</
"ClusterTemplate"
"{Binding Count}"
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.
class
PushpinsGroup
private
List<Pushpin> _pushpins =
new
List<Pushpin>();
Point MapLocation {
get
;
set
; }
PushpinsGroup(Pushpin pushpin, Point location)
_pushpins.Add(pushpin);
MapLocation = location;
FrameworkElement GetElement(DataTemplate clusterTemplate)
if
(_pushpins.Count == 1)
return
_pushpins[0];
// more pushpins
Pushpin()
// just need the first coordinate
GeoCoordinate = _pushpins.First().GeoCoordinate,
Content = _pushpins.Select(p => p.DataContext).ToList(),
ContentTemplate = clusterTemplate,
};
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.
GeneratePushpins()
List<PushpinsGroup> pushpinsToAdd =
List<PushpinsGroup>();
(var pushpin
_pushpins)
bool
addGroup =
true
var newGroup =
PushpinsGroup(pushpin, _map.ConvertGeoCoordinateToViewportPoint(pushpin.GeoCoordinate));
(var pushpinToAdd
pushpinsToAdd)
double
distance = pushpinToAdd.MapLocation.GetDistanceTo(newGroup.MapLocation);
(distance < MAXDISTANCE)
pushpinToAdd.IncludeGroup(newGroup);
false
break
(addGroup)
pushpinsToAdd.Add(newGroup);
_map.Dispatcher.BeginInvoke(() =>
_map.Layers.Clear();
MapLayer layer =
MapLayer();
(var visibleGroup
pushpinsToAdd.Where(p => _map.IsVisiblePoint(p.MapLocation)))
var cluster = visibleGroup.GetElement(
.ClusterTemplate)
as
Pushpin;
(cluster !=
null
)
layer.Add(
MapOverlay() { GeoCoordinate = cluster.GeoCoordinate, Content = cluster.Content, ContentTemplate = cluster.ContentTemplate});
(layer.Count > 0)
_map.Layers.Add(layer);
});
The extension method GetDistanceTo is the algorithm to calculate the distance between two points:
static
GetDistanceTo(
Point p1, Point p2)
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:
IsVisiblePoint(
Map map, Point point)
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 =
ClustersGenerator(map, pushpins,
.Resources[
]
DataTemplate);
You can download all code here.
Congratulations on winning the gold! blogs.technet.com/.../technet-guru-awards-june-2013.aspx
Your article was featured here on MSDN blogs: blogs.msdn.com/.../windows-phone-guru-windows-phone-8-development-maps-and-clusters.aspx
Thanks!
Thank you Ed!
You're welcome Tiziano! Your article was also featured on the home page of TNWiki:
social.technet.microsoft.com/wiki
Finally your article was featured on the Wiki Ninjas blog here: blogs.technet.com/.../june-windows-phone-guru-tiziano-cacioppolini-brings-us-quot-windows-phone-8-development-maps-and-clusters-quot.aspx
Thanks again!
This is great! Thanks
I'm getting an Access Violation message after I zoom in/out a few times? I've tried every thing to fix it, but no luck.
Have you experienced this fault, it happen on your example project too??
Please help, it's killing me!!!
"Access violation" seems related to cross-thread exception. Have you tried to add some more "dispatcher"?.
I will check as soon as possible
You should try to use _map.ResolveCompleted instead of all other events (viewchanged, centerchanged,...) I tried your code with a lot of pushpin (519 in my case) and it can slow down the application because events like centerchanged,... are fired multiple times.
The ResolveCompleted occurs when the Map control enters the idle state and it does the job.
What is your thought?
Btw, thank you for your code.
Thanks Fabrice, I updated the file zip with your suggestion.
Ed Price can you help me to update the article? It lose all format when I save the new version.