Newton Zou Newton Zou - 27 days ago 12
C# Question

How to know a StreamGeometry is a Line

I have a collection of StreamGeometrys. Each StreamGeometry could be any shape. I just want to pick up the StreamGeometrys that are lines. How can I know a StreamGeometry is a line.

Background:
I get collection of StreamGeometrys from an interface. The interface is given by other team and doesn't return the information that what the Geometry is. (I may discuss with them to make an update, but it involve the architecture change.) I have a function to group the Geometrys and get the outline of the group. Originally, I add them to a GeometryGroup, then I use Geometry.GetOutlinedPathGeometry to get the outline of the GeometryGroup. The behavior is good.

Problem:But when the Geometrys number increases to more than 200, GetOutlinedPathGeometry becomes extremely slow.

Current solution:Then I have to make a compromise to get an outline not that accurate. Instead of GetOutlinedPathGeometry, I use Geometry.Combine to combine the Geometrys. Geometry.Combine is mainly used for closed Geometry. When a Geometry is not closed, it will find the nearest points to make it closed. This behavior somehow reduce the complexity of the overall outline that GetOutlinedPathGeometry can get a result quicker.

How I get to this question:But one problem is that Geometry.Combine will omit Line Shapes. After combine all Geometrys, Lines are not in the group. So I want to find those lines and use GetOutlinedPathGeometry to get their outlines first. Then use their outlines to combine with others.

Also, even I have the Geometry type information, it could not help too much. Because Geometry.Combine will omit any Line Shapes that can be made of any Geometry type. Line, Path even Rectangle can all make a Line shape.

Other attempts:I tried to use GetOutlinedPathGeometry to get outline for each geometry first, then to combine. The performance improves a little bit but still very slow.

Answer

Just drop this code in and you can use StreamGeometry.IsLine() to check for lines.

public static class StreamGeometryExtensions
{
    public static bool IsLine(this StreamGeometry sg, double sampleRate = 0.2, decimal tolerance = 0
    {
        PathGeometry g = sg.GetFlattenedPathGeometry();
        if (g.MayHaveCurves())
        {
            return false;
        }

        Point origin, originTangent;
        Point end, endTangent;
        g.GetPointAtFractionLength(0, out origin, out originTangent);
        g.GetPointAtFractionLength(1, out end, out endTangent);

        Vector originToEnd = end - origin;

        for (double i = 0; i < 1; i += sampleRate)
        {
            Point current, currentTangent;
            g.GetPointAtFractionLength(i, out current, out currentTangent);

            Vector currentToEnd = end - current;
            Vector originTocurrent = current - origin;

            decimal l1 = (decimal)(originTocurrent.Length + currentToEnd.Length);
            decimal l2 = (decimal)originToEnd.Length;

            if (Math.Abs(l2 - l1) > (l2 * tolerance))
            {
                return false;
            }
        }
        return true;
    }
}

The code "samples" the path every X% (example 0.2 = 20%) and checks if the origin, the end and the current sample point are on a line.

The code is a very general solution to the problem and can definetly be optimized for specific scenarios where performance is important.

I measured the performance for following Paths: (sampleRate = 0.2, tolerance = 0)

    <Path Data="M 217,172 L 10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300,  10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300,  10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300,  10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300"></Path>
    <Path Data="M 217,172 L 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20"></Path>
    <Path Data="M 217,172 L 10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300,  10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300,  10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300,  10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300"></Path>
    <Path Data="M 217,172 L 10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300,  10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300,  10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300,  10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300"></Path>
    <Path Data="M 217,172 L 10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300,  10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300,  10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300,  10 50, 50 30, 70 40, 100 300 10 50, 50 30, 70 40, 100 300"></Path>
    <Path Data="M 217,172 L 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20, 20 20"></Path>

On an i7 4790 i got these times:

80µs - False
390µs - True
86µs - False
82µs - False
69µs - False
355µs - True