Thursday, December 4, 2008

OpenStreetMap rendering in Silverlight part VI

I've spent some time looking at performance, using some of the tricks from Seema's blog - she also very helpfully sent me some helpful tips when trying to build applications with many complex shapes.

Changes to the version now include:
  • Speed/Memory improvement using feature clipping.
  • Tooltips tags for features, (mouse over a line or area to see)


The neeed for speed: performance investigation
This is where the ability to swap drawers has been helpful, I was able to create try out new ideas without having to worry about breaking the existing drawing system. One of the technique's I investigated was drawing using Silverlight's mini-language, this allows you to draw complex shapes using a LOGO like definition:

<Canvas>
<Path Stroke="Black" Fill="Gray"Data="M 10,100 C 10,300 300,-200 300,100" />
</Canvas>

Since I need to draw this dynamically I need to load the Path.Data from a string:

StringBuilder sb = new StringBuilder();
sb.Append("<Path xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"");
sb.Append(" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"");
sb.Append(" Data=\"");
sb.Append("");

ShowWay(sb, tileBorder);
foreach (var gpp in quadrant.GeoPolyPoints)
DrawFeature(sb, gpp);

sb.Append("\"/>");

GeometryGroup gg = new GeometryGroup();
Path uiPath = (Path)System.Windows.Markup.XamlReader.Load(sb.ToString());

using a helper function to turn a set of points into a set of draw instructions:

private void DrawFeature(StringBuilder sb, GeoPolyPoint gpp)
  {

   bool isFirst = true;
   foreach (GeoPoint geoPoint in gpp.GeoPoints)
   {
    int y = geoPoint.GetScaledX(viewport);
    int x = geoPoint.GetScaledY(viewport);

    if (isFirst)
    {
     isFirst = false;
     sb.Append(" M ");
    }
    else
     sb.Append("L ");

    sb.Append(x);
    sb.Append(',');
    sb.Append(y);
   }
   if (gpp.PolyConstruct == PolyConstruct.Polygon)
    sb.Append("Z ");

   return count;
  }

I then timed this against my previous attempt, and found hardly any difference - and I also tried create PathFigures, Geometries etc - these were painfully slow (mainly due to the need to create lots of LineSegment objects). So I am sticking with creating Shapes (Polygon and Polyline) as both of these take a collection of points, so no need to create lots of lines.

I then had a look at Seema's blog again and had a play with
<param name="enableRedrawRegions" value="true" />

This showed that I was updating a vast region of the screen, this seems to be related to clipping not behaving as expected - I've asked Seema for some clarification on this. However I also knew my draw routine was pretty lazy - it did not check when drawing a feature if it would even be visible on the tile (for example to the left of the tile).

I was relying on Silverlight clipping to do all the work. So I put in a simple check to only draw features that actually appear in the tile. This reduced the New Malden draw set from 26,038 features to 7,979. This means my code is drawing 3 times less shapes - a massive memory and speed improvement (10% faster), but more importantly ~20,000 shapes that Silverlight does not have to calculate, rotate, clip for no visible effect.

Once I get an answer from Seema on the clipping issue things should get even quicker!

1 comment:

Nguyễn Ngọc Tú said...

use http://www.codeplex.com/StringToPathGeometry

to cover String To PathGeometry