One of the first apps I created for Windows Phone with 25K was one called Maps+. It’s still available, but it definitely has some flaws – namely the Bing Soap API was finicky (yes I should have used the REST API), and didn’t seem to work depending on where you were in the world.
Despite the issues, one of the parts that I was rather proud of was the map optimization and performance. If you’ve used mapping apps on WP7 before, you’ve most likely run into lag and stuttering. Not to say my app was perfect in this regard, but I feel like I found a few good tricks to eek out some more frames.
1. Use CacheMode=”BitmapCache” on map markers for the love of god
If you don’t enable this setting on any controls (ie pushpins or other markers) that you add to the map, the gpu will redraw each control every time you zoom or pan the map. This is one of the main causes for lag in map apps, and it pains me because it’s so easy to fix! Just add CacheMode=”BitmapCache” to controls you add to the Map control. Of course too many controls and you start eating into GPU memory, but that’s another story. A good article about this here.
2. If you are plotting directions (using MapPolyLine) – use the Douglas Peucker Line Approximation algorithm to reduce the amount of points that you have to render. At lower zoom levels, you should only need to render a fraction of the points given in order to display an accurate path, so why waste cycles on the extras you can’t even see? Craig Selbert has a great implementation of the DPAA here. I used this and adapted it slightly to work with the WP7 Map control:
public class Utility
{
/// <summary>
/// Uses the Douglas Peucker algorithim to reduce the number of points.
/// </summary>
/// <param name="Points">The points.</param>
/// <param name="Tolerance">The tolerance.</param>
/// <returns></returns>
public static List<Location> DouglasPeuckerReduction(List<Location> Points, Double Tolerance)
{
if (Points == null || Points.Count < 3)
return Points;
Int32 firstPoint = 0;
Int32 lastPoint = Points.Count - 1;
List<Int32> pointIndexsToKeep = new List<Int32>();
//Add the first and last index to the keepers
pointIndexsToKeep.Add(firstPoint);
pointIndexsToKeep.Add(lastPoint);
//The first and the last point can not be the same
while (Points[firstPoint].Equals(Points[lastPoint]))
{
lastPoint--;
}
DouglasPeuckerReduction(Points, firstPoint, lastPoint, Tolerance, ref pointIndexsToKeep);
List<Location> returnPoints = new List<Location>();
pointIndexsToKeep.Sort();
foreach (Int32 index in pointIndexsToKeep)
{
returnPoints.Add(Points[index]);
}
return returnPoints;
}
/// <summary>
/// Douglases the peucker reduction.
/// </summary>
/// <param name="points">The points.</param>
/// <param name="firstPoint">The first point.</param>
/// <param name="lastPoint">The last point.</param>
/// <param name="tolerance">The tolerance.</param>
/// <param name="pointIndexsToKeep">The point indexs to keep.</param>
private static void DouglasPeuckerReduction(List<Location> points, Int32 firstPoint, Int32 lastPoint, Double tolerance, ref List<Int32> pointIndexsToKeep)
{
Double maxDistance = 0;
Int32 indexFarthest = 0;
for (Int32 index = firstPoint; index < lastPoint; index++)
{
Double distance = PerpendicularDistance(points[firstPoint], points[lastPoint], points[index]);
if (distance > maxDistance)
{
maxDistance = distance;
indexFarthest = index;
}
}
if (maxDistance > tolerance && indexFarthest != 0)
{
//Add the largest point that exceeds the tolerance
pointIndexsToKeep.Add(indexFarthest);
DouglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance, ref pointIndexsToKeep);
DouglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance, ref pointIndexsToKeep);
}
}
/// <summary>
/// The distance of a point from a line made from point1 and point2.
/// </summary>
/// <param name="pt1">The PT1.</param>
/// <param name="pt2">The PT2.</param>
/// <param name="p">The p.</param>
/// <returns></returns>
public static Double PerpendicularDistance(Location Point1, Location Point2, Location Point)
{
//Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)| *Area of triangle
//Base = √((x1-x2)²+(x1-x2)²) *Base of Triangle*
//Area = .5*Base*H *Solve for height
//Height = Area/.5/Base
Double area = Math.Abs(.5 * (Point1.Latitude * Point2.Longitude + Point2.Latitude * Point.Longitude + Point.Latitude * Point1.Longitude - Point2.Latitude * Point1.Longitude - Point.Latitude * Point2.Longitude - Point1.Latitude * Point.Longitude));
Double bottom = Math.Sqrt(Math.Pow(Point1.Latitude - Point2.Latitude, 2) + Math.Pow(Point1.Longitude - Point2.Longitude, 2));
Double height = area / bottom * 2;
return height;
}
}
3. Another point about using MapPolyLine, is that it draws the entire line, even if parts of the line are off the screen. This can be quite painful for a large route, especially if you are zoomed in pretty close. I found that clipping the route to the screen helped in reducing the rendering hit. This one is pretty easy to implement as well:
MapPolyLine routeLine = new MapPolyLine();
routeLine.Clip = new RectangleGeometry() { Rect = new Rect(0,0,480,800) };
Anyways, hope those tips helped!
- Tommy