Search code examples
c#wpfvector-graphics

How to handle a large amount of geometry data in a WPF drawing?


I am drawing some items to a DrawingGroup in a WPF UI I am experimenting with, the code looks a little like this:

foreach( var path in allRoads ) 
{
    var wpfPen = new Pen(brush, penSize);
    GeometryDrawing geometry = new GeometryDrawing();
    geometry.Pen = wpfPen;
    geometry.Geometry = GetPathGeometry(path);
    drawingGroup.Children.Add(geometry);
}

This is called repeatedly as I draw in the various geometry items I am rendering ( I am adding them to the DrawingGroup then asking the DrawingContext to draw it afterwards ) but it gets into trouble and crashes out with the following message:

The thread 0x3228 has exited with code 0 (0x0). Exception thrown: 'System.OverflowException' in PresentationCore.dll An unhandled exception of type 'System.OverflowException' occurred in PresentationCore.dll The image data generated an overflow during processing.

The crash seems to be happening just short of the 38000th geometry item ( all of them mult-point paths, some probably quite long as this is GIS data ) being added to the DrawingGroup.Children collection.

I'm guessing that this is probably pushing the system past what it is designed for - is there a better way to handle this within WPF? Can it deal with a lot of geometry data and if not, whereabouts are the limits?


Solution

  • You could use the Non-parametric Ramer-Douglas-Peucker algorithm to simplify the collection of points before trying to render them. This would keep the same basic shape, but greatly reduce the detail (detail that won't even be visible at the zoom levels you are talking about).

    Pseudo-code from Wikipedia:

    function DouglasPeucker(PointList[], epsilon)
        // Find the point with the maximum distance
        dmax = 0
        index = 0
        end = length(PointList)
        for i = 2 to ( end - 1) {
            d = perpendicularDistance(PointList[i], Line(PointList[1], PointList[end])) 
            if ( d > dmax ) {
                index = i
                dmax = d
            }
        }
        // If max distance is greater than epsilon, recursively simplify
        if ( dmax > epsilon ) {
            // Recursive call
            recResults1[] = DouglasPeucker(PointList[1...index], epsilon)
            recResults2[] = DouglasPeucker(PointList[index...end], epsilon)
    
            // Build the result list
            ResultList[] = {recResults1[1...length(recResults1)-1], recResults2[1...length(recResults2)]}
        } else {
            ResultList[] = {PointList[1], PointList[end]}
        }
        // Return the result
        return ResultList[]
    end
    

    Here is a link to a C# implementation.

    You could dynamically adjust the epsilon value based on the zoom level to keep things looking correct no matter how close or far you are zoomed.