?

Log in

No account? Create an account
Windows Phone: HospitalPrices released, how to show 3000+ markers on a Map - Greg [entries|archive|friends|userinfo]
Greg

[ website | gregstoll.com ]
[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

Links
[Links:| * Homepage * Mobile apps (Windows Phone, Win8, Android, webOS) * Pictures * LJBackup * Same-sex marriage map * iTunesAnalysis * Where's lunch? ]

Windows Phone: HospitalPrices released, how to show 3000+ markers on a Map [Jun. 12th, 2013|09:38 pm]
Greg
[Tags|, , ]
[Current Mood |happyhappy]

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.
LinkReply