DeepEarth is an interesting Silverlight project. It allows interactive tile-based maps to run in a browser with overlays of custom data, however it suffers from some performance problems.

Large GPS Track Logs

I had the need to display GPS tracks in DeepEarth. GPS tracks can contain thousands of points. DeepEarth has three update modes called ElementUpdate, PanOnlyUpdate and TransformUpdate for showing features such as tracks:

  • ElementUpdate recalculates the point positions on every map movement. This produces an accurate track display but gets slower as the number of points increases.
  • PanOnlyUpdate recalculates point positions during panning and hides features while zooming. Not too useful for me and didn’t seem to show anything anyway.
  • TransformUpdate draws the tracks to the map once then scales and pans the vector graphic in synchronization with the map. This makes it very fast. Sadly the scaling code is flawed. Lines disappear as you zoom in and sections of the tracks become distorted, almost looking like calligraphy.

I wasted many evenings trying to get the scaling in TransformUpdate mode working before giving up. I then turned my attention back to the ElementUpdate mode to see where the bottleneck is.

The LogicalToPixel Function

LineStringControl objects represent the GPS tracks and they draw as lines using multiple points. When points are added to a LineStringControl they are transformed from longitude and latitude into logical coordinates. This is an intermediate coordinate system that can easily be converted into pixel coordinates later.

When the map is panned or zoomed the CoordTransform.LogicalToPixel function is called for every point in line. This function uses some simple mathematics to work out where the point should be on the map in terms of pixels. This is the bottleneck.

A single zoom or pan operation can result in this function being called several times. If a GPS track has 7,000 points that is a lot of calls. My testing showed that even the very first mathematical operation caused considerable slow down. Overall this function had several problems:

  • Using 64-bit division
  • Creating three Point objects and then discarding two for each call
  • Using Math.Log and Math.Pow functions

Optimizing LogicalToPixel

The first step was to separate out the mathematical operations into two groups:

  • Those that can be executed once per movement of the map
  • Those that have to be executed once per point

I split the function into two new functions, LogicalToPixelPrepare and LogicalToPixelPerform to allow the two groups to be called separately.

Next instead of creating two temporary Point objects and then throwing them away, two global (to the class) Point objects are created and continually reused. This eliminates any overhead of object creation.

Finally I saw that I could eliminate both Math.Log and Math.Pow, as they were not necessary.

The last step was to modify the GeometryLayer.UpdatePathData function to call LogicalToPixelPrepare once and then LogicalToPixelPerform for every point. This reduces the transformation code needed for every point down to four multiplications and two additions.

Conclusion & Download

By finding the bottleneck and looking at way to optimize it, I was able to gain a significant speed increase for my 7,000 point test GPS track. I think the performance is close to that which could be obtained from the TransformUpdate mode, but without the scaling flaws found in that mode.

Download a patch that can be applied to the Silverlight 4 branch of DeepEarth (currently at revision 49789).