This project has moved. For the latest updates, please go here.

Rounding Errors in MapPolyLine

Sep 19, 2013 at 5:52 PM
Hey guys,

I've noticed a weird issue when you're really zoomed in. If you bind a MapPolyLine to a collection of Locations, you get weird rounding issues when you zoom in or pan around. I'll throw in some screenshots to give you an idea of what I'm talking about.

The green dots are at the same positions as where the line should be. They lines pop and jerk too, which is hard to capture in a screenshot. I can upload a sample project if necessary.

Image
Image

Thanks,
-Free
Coordinator
Sep 19, 2013 at 7:23 PM
I have already observed this kind of behaviour. It is a result of very large scaling factors in the map's ViewportTransform and does not only emerge with MapPolylines, but with any kind of map overlays when zoomed in to high zoom levels. There is not really much that could be done to avoid this behaviour, as it is inherent in geometry transforms in WPF. A workaround might be to write a different MapPolyline class that does not use WPF transforms at all, but recalculates all the viewport coordinates each time the viewport changes.
Sep 19, 2013 at 9:28 PM
Heh, that's really annoying! I just did some research and it seems like other people have had that issue when trying to use their own unit system. Here's the one that really stuck with me. http://social.msdn.microsoft.com/Forums/vstudio/en-US/ae664c74-b6de-4d55-a673-b23889bd6389/rendering-limits-of-point-structure-and-rounding-errors-in-transforms

Seems like we're just pushing WPFs API to the limits. Telling it to do things that it wasn't designed for. I'm guessing there is no easy way to reform the math to do everything scaled locally? I'm willing to investigate this and put some leg work into it, if you have any suggestions on where to start looking and brainstorming.
Coordinator
Sep 20, 2013 at 11:26 AM
No, there is obviously no easy way to "reform the math". For the purpose of writing your own polyline control that reacts on the map's ViewportChanged event you may start looking into the LocationToViewportPoint method in class MapBase. It just does
return ViewportTransform.Transform(mapTransform.Transform(location));
where mapTransform is a MercatorTransform instance that transforms locations to logical points, and ViewportTransform is the transform mentioned above which transforms logical points to viewport points (and apparently has the rounding problem). You may now delve into how ViewportTransform is calculated in class TileContainer and try to resemble the maths in a way that avoids the rounding issue.
Oct 16, 2013 at 9:26 PM
Hey dude!

We were able to figure out a workaround for this. We cloned MapPolyline and replaced the StreamGeometry with a pen. It's probably a bit more expensive, but it doesn't jitter! We call it MapPolypen. You want a copy of it? And if so, how can I get you a pull request or whatever?
Coordinator
Oct 16, 2013 at 10:05 PM
Just post it here and I'll have a look.
Oct 17, 2013 at 12:01 AM
Here's a Google Drive link to all of the zipped files. I made some small edits to MapPath for this so I included those. If you can think of a clever way to get around the need to edit MapPath, then you can probably remove any changes.

https://docs.google.com/file/d/0BzbZiT_fBJPsaktkYW4yd3RZbEU/edit?usp=sharing
Coordinator
Oct 17, 2013 at 9:34 AM
Edited Oct 17, 2013 at 2:16 PM
Your workaround is a typical "it solves my special problem" solution.
  1. Although your MapPolypen still derives from MapPath, it completely overrides its Geometry handling.
  2. It ignores the Fill property.
  3. It ignores the IsClosed property, hence does not draw closed polygons.
As you have already copied a lot of code from MapOverlay, it's hard to understand why you didn't come up with the obvious solution, which would be just deriving a Polyline class from MapOverlay. It could look as simple as this:
public class MapPolylineOverlay : MapOverlay
{
    public static readonly DependencyProperty LocationsProperty =
        DependencyProperty.Register(
            "Locations", typeof(IEnumerable<Location>), typeof(MapPolylineOverlay),
            new FrameworkPropertyMetadata(
                null, FrameworkPropertyMetadataOptions.AffectsRender));

    [TypeConverter(typeof(LocationCollectionConverter))]
    public IEnumerable<Location> Locations
    {
        get { return (IEnumerable<Location>)GetValue(LocationsProperty); }
        set { SetValue(LocationsProperty, value); }
    }

    protected override void OnViewportChanged()
    {
        InvalidateVisual();
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        if (ParentMap != null && Locations != null)
        {
            Location loc1 = null;
            foreach (Location loc2 in Locations)
            {
                if (loc1 != null)
                {
                    drawingContext.DrawLine(Pen,
                        ParentMap.LocationToViewportPoint(loc1),
                        ParentMap.LocationToViewportPoint(loc2));
                }
                loc1 = loc2;
            }
        }
    }
}