Windows Phone: HospitalPrices released, how to show 3000+ markers on a Map

I just released HospitalPrices for Windows Phone. One of the more interesting parts was figuring out how to put 3000+ markers on a Map control. My first attempt was putting all the markers on the Map, but that ran out of memory. After some more tinkering, here’s what I ended up with. It runs pretty smoothly on my Lumia 920 – if it needed to run faster I could have implemented a quad tree to search for markers instead of checking all 3000+ of them every time.

Want to make your own app/website? Check out the SQLite database with all the data!

Prerequisites: I’m using the Windows Phone 8 Map control – MarkerMap is the Map control, and every time the center or zoom level changes we call UpdatePins().


using Microsoft.Phone.Maps.Controls;
using System.Device.Location;

GeoCoordinate _lastUpdatedTopLeft = null;
GeoCoordinate _lastUpdatedBottomRight = null;
MapLayer _pinLayer = new MapLayer();
private void Init()
{
MarkerMap.Layers.Add(_pinLayer);
}
public bool CoordInBounds(GeoCoordinate coord,
GeoCoordinate topLeft,
GeoCoordinate bottomRight)
{
return (coord.Longitude >= topLeft.Longitude &&
coord.Longitude <= bottomRight.Longitude &&
coord.Latitude <= topLeft.Latitude &&
coord.Latitude >= bottomRight.Latitude);
}
private void UpdatePins()
{
const double ZOOM_LEVEL_THRESHOLD = 9.0;
if (MarkerMap.ZoomLevel >= ZOOM_LEVEL_THRESHOLD)
{
const int MAP_MARKER_MARGIN = 150;
GeoCoordinate neededTopLeft =
MarkerMap.ConvertViewportPointToGeoCoordinate(
new Point(-1 * MAP_MARKER_MARGIN, -1 * MAP_MARKER_MARGIN));
GeoCoordinate neededBottomRight =
MarkerMap.ConvertViewportPointToGeoCoordinate(
new Point(MarkerMap.ActualWidth + MAP_MARKER_MARGIN,
MarkerMap.ActualHeight + MAP_MARKER_MARGIN));
// See if we already have all the necessary markers
if (_lastUpdatedTopLeft != null &&
CoordInBounds(neededTopLeft, _lastUpdatedTopLeft, _lastUpdatedBottomRight) &&
CoordInBounds(neededBottomRight, _lastUpdatedTopLeft, _lastUpdatedBottomRight))
{
return;
}
var existingIdsList = _pinLayer.Select(
(overlay) => (int)(((FrameworkElement)overlay.Content).Tag));
HashSet<int> existingIds = new HashSet<int>();

foreach (var id in existingIdsList)
{
existingIds.Add(id);
}
Collection<int> indicesToRemove = new Collection<int>();
Collection<MapOverlay> overlaysToAdd = new Collection<MapOverlay>();
// TODO - this is the entire collection of markers. Each has an integer
// Id and a Latitude and Longitude, as well as a PinBrush which is the
// color of their marker.
var datas = GetHospitalBasicData();
_lastUpdatedTopLeft = MarkerMap.ConvertViewportPointToGeoCoordinate(
new Point(-2 * MAP_MARKER_MARGIN, -2 * MAP_MARKER_MARGIN));
_lastUpdatedBottomRight = MarkerMap.ConvertViewportPointToGeoCoordinate(
new Point(MarkerMap.ActualWidth + 2 * MAP_MARKER_MARGIN,
MarkerMap.ActualHeight + 2 * MAP_MARKER_MARGIN));
// Check existing markers
for (int i = 0; i < _pinLayer.Count; ++i)
{
GeoCoordinate coord = _pinLayer[i].GeoCoordinate;
if (!CoordInBounds(coord, _lastUpdatedTopLeft, _lastUpdatedBottomRight))
{
indicesToRemove.Add(i);
}
}
foreach (var data in datas)
{
if (!existingIds.Contains(data.Id))
{
GeoCoordinate coord = data.Coordinate;
if (CoordInBounds(coord, _lastUpdatedTopLeft, _lastUpdatedBottomRight))
{
MapOverlay overlay = new MapOverlay();
Ellipse e = new Ellipse()
{
Fill = data.PinBrush,
Height = 35,
Width = 35,
Stroke = new SolidColorBrush(Colors.Black),
StrokeThickness = 3,
Tag = data.Id
};
overlay.Content = e;
overlay.GeoCoordinate = coord;
overlaysToAdd.Add(overlay);
}
}
}
// Now, switch them out.
int numToReplace = Math.Min(indicesToRemove.Count, overlaysToAdd.Count);
for (int i = 0; i < numToReplace; ++i)
{
_pinLayer[indicesToRemove[i]] = overlaysToAdd[i];
}
if (indicesToRemove.Count > numToReplace)
{
int offset = 0;
// We know that indicesToRemove is sorted
for (int i = numToReplace; i < indicesToRemove.Count; ++i)
{
_pinLayer.RemoveAt(indicesToRemove[i] - offset);
offset += 1;
}
}
else if (overlaysToAdd.Count > numToReplace)
{
for (int i = numToReplace; i < overlaysToAdd.Count; ++i)
{
_pinLayer.Add(overlaysToAdd[i]);
}
}
else
{
_pinLayer.Clear();
_lastUpdatedTopLeft = null;
_lastUpdatedBottomRight = null;
_lastPinColorType = null;
}
}

See all my Windows Phone development posts.

I’m planning on writing more posts about Windows Phone development – what would you like to hear about? Reply here, on twitter at @gregstoll, or by email at greg@gregstoll.com.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s