5

I've got two huge (>100000 items) collections of PathGeometry I need to compare using PathGeometry.Combine something like this:

List<PathGeometry> firstList;
List<PathGeometry> secondList;
[...]

foreach (PathGeometry pg1 in firstList)
  foreach (PathGeometry pg2 in secondList)
  {
    PathGeometry intergeo = PathGeometry.Combine(pg1, pg2, GeometryCombineMode.Intersect, null);
    if (intergeo.GetArea() > 0)
    {
      // do whatever with intergeo.GetArea()
    }
  }

To my surprise PathGeometry is part of the GUI and does use the dispatcher, which sometimes causes problems since my calculations do run in some background thread without GUI using Parallel.ForEach()

So I'm looking for an alternative to PathGeometry which is not connected to the GUI.
My figures are quite complex and add a lot of PathFigures to the PathGeometry.Figures.

I'm creating those PathGeometries myself from some bloated government xml files, so it would be no problem to create something else instead. But I need a function to create an intersection of two of these geometries (not adding them to each other) to get the area which both geometries cover, such as the red area in this diagram:

enter image description here

Rachel
  • 130,264
  • 66
  • 304
  • 490
Sam
  • 28,421
  • 49
  • 167
  • 247
  • What about parsing your `Path.Data` into a string using the [geometry "mini-langauge"](http://rcosic.wordpress.com/2009/08/11/wpf-geometry-mini-language/) instead of using the `PathGeometry` object? – Rachel Sep 25 '12 at 13:03
  • I read my data from some custom data source and am building the path on the fly. I might as well build this mini-Language-string, but how would I for example use PathGeometry.Combine on this mini-data-strings without creating PathGeometry objects?? – Sam Sep 25 '12 at 15:54
  • Are you tried using `GeometryGroup` or `AddGeometry` instead of `Geometry.Combine`? Per MSDN's page on [Geometry.Combine](http://msdn.microsoft.com/en-us/library/ms607449.aspx), "Careful thought should be involved when using Combine to perform a union as it can be very CPU-expensive. In most cases, a GeometryGroup or AddGeometry will work better." – Rachel Sep 26 '12 at 15:09
  • I would if I could. But I am not using Combine to Union two pathfigures, I'm using it to Intersect them instead, which can't be done with AddGeometry. – Sam Sep 27 '12 at 09:08
  • You may also want to ask on the [GIS Stack Exchange](http://gis.stackexchange.com/). – Rawling Sep 27 '12 at 13:31

3 Answers3

3

What about parsing your Path.Data into a string using the geometry "mini-langauge" instead of using the PathGeometry object?

For example, instead of creating something like this:

<Path Stroke="Black">
    <Path.Data>
        <PathGeometry>
            <PathFigure IsClosed="true" StartPoint="10,100">
                <LineSegment Point="100,100" />
                <LineSegment Point="100,50" />
            </PathFigure>
        </PathGeometry>
    </Path.Data>
</Data>

You could create

<Path Stroke="Black" Data="M 10 100 L 100 100 L 100 50 Z" />

Or in your case, the XAML would probably look something like this:

<Path Stroke="Black" Data="{Binding MyPathProperty}" />

The Path.Data is a string that is expected to be in a specific format, and is a shorter way of drawing your path's geometry.

Here's a list of what letters mean, and what parameters are expected, taken from the link above:

  • F value - Sets the Geometry.FillRule property. Use 0 for EvenOdd, or 1 for NonZero. This command must appear at the beginning of the string (if you decide to use it).

  • M x,y - Creates a new PathFigure for the geometry and sets its start point. This command must be used before any other commands except F. However, you can also use it during your drawing sequence to move the origin of your coordinate system. (The M stands for move).

  • L x,y – Creates a LineSegment to the specified point.

  • H x – Creates a horizontal LineSegment using the specified X value and keeping the Y value constant.

  • V y - Creates a vertical LineSegment using the specified Y value and keeping the X value constant.

  • A radiusx, radiusY, degrees isLargeArch, isClockwise x,y – Creates an ArcSegment to the indicated point. You specify the radii of the ellipse that describes the arc, the number of degrees the arc is rotated, and Boolean flags that set the IsLargeArc and SweepDirection properties.

  • C x1,y1 x2,y2 x,y - Creates a BezierSegment to the indicated point, using control points at (x1, y1) and (x2, y2).

  • Q x1,y1 x,y - Creates a QuadraticBezierSegment to the indicated point, with one control point at (x1, y1).

  • S x2,y2 x,y - Creates a smooth BezierSegment by using the second control point from the previous BezierSegment as the first control point in the new BezierSegment.

  • Z - Ends the current PathFigure and sets IsClosed to true. You don’t need to use this command if you don’t want to set IsClosed to true – instead, simply use M if you want to start a new PathFigure or end the string.

For example, the string used in the sample above (M 10 100 L 100 100 L 100 50 Z) can be broken down into:

  • M 10 100 - Start Path at 10,100
  • L 100 100 - Draw a line to 100,100
  • L 100 50 - Draw a line to 100,50
  • Z - End String

In your case where your Path.Data is read from the database, you would start by creating a string containing M x y where x y is the x,y starting position for the path, and then read your segments one at a time and append them to the string using the shorthand above, then end your string with Z

Note that upper and lower case letters have different meanings. An uppercase letter means you are providing an absolute value for your path segment, while a lowercase letter means it should be relative to the last segment.

For example, if your segment says "L 10 10", it means draw a line to position 10,10 on the grid while "l 10 10" means draw a line 10 up and 10 right from the current position.

In your case where your PathGeometry is read from the database, you would need to convert each PathGeometry into a string, then combine the strings.

Here's some rough code as an example. I know it doesn't work, but hopefully it can point you in the right direction

Edit

Based on your edited question, it sounds like your data item is stored as a PathGeometry, so you may need to convert your PathGeometry objects to a string, then just combine the strings

Here's a very rough example. I'm fairly sure I have some syntax wrong and possibly a bit of the logic as well since I'm not that familiar with the PathGeometry object, but hopefully it can point you in the right direction

foreach (PathGeometry pg1 in firstList)
    foreach (PathGeometry pg2 in secondList)
    {
        var s1 = ConvertGeometryToString(pg);
        var s2 = ConvertGeometryToString(pg2);

        // Ideally you probably wouldn't want this until you have your 
        // full PathGeometry string built, but I'm not sure what you're doing
        // with the object so left it in anyways
        PathGeometry intergeo = Geometry.Parse(s1 + s2);

    }
}


string ConvertGeometryToString(PathGeometry pg)
{
    StringBuilder sb = new StringBuilder();

    foreach(var figure in pg.PathFigures)
    {
        sb.Append("M " + figure.StartPoint);

        foreach(var seg in figure.Segments)
        {
            if (seg is LineSegment)
                sb.Append(" L " + ((LineSegment)seg).Point);

            else if (seg is ArcSegment)
            ... etc

        }

        if (figure.IsClosed)
            sb.Append(" Z");
    }

    return sb.ToString();
}
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • 1
    What I need most is to find intersections of different (complex) geometries - so how do I run something like (Path)Geometry.Combine on this kind of data? – Sam Sep 26 '12 at 08:51
  • @Sam Can you give an example of what kind of data you start with from your data source? – Rachel Sep 26 '12 at 11:31
  • it is mapping data. For example the border of a piece of farming land with a hole for a pond in the middle, which I need to intersect with some other multy-polygon area. The data source is some bloated government xml file. The example shape of the area above has 2702 lines of xml and I would need the second shape to intersect, too. I could try to cut the bloatware out and reduce it somehow if this really could be useful. – Sam Sep 26 '12 at 13:43
  • @Sam I'm not familiar with `Geometry.Combine` or how it works exactly, but if this doesn't work you may be able to combine your objects at a different [DispatcherPriority](http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority.aspx) to ensure they get run at a specific time – Rachel Sep 26 '12 at 13:59
  • neither got I any clue about the inner works of Combine, thats why I need some class which provides this functionality. And since these are not only very bloated xml files but large numbers to compare (>500000 shapes in my example data), too, this code needs to run on every cpu core available. I don't think Dispatching does scale this well - the whole point of my question was to avoid it. – Sam Sep 26 '12 at 14:16
  • @Sam Perhaps you could edit your question to include a small sample of how the data looks in your data source? I'm still a bit unclear if you're storing `PathGeometry` objects or only segments, and if segments, how they appear in the database. – Rachel Sep 26 '12 at 14:18
  • @Sam And what about using [TPL](http://msdn.microsoft.com/en-us/library/dd460717.aspx) for background work? You could build your items in the background using `Task.Factory.StartNew`, and then `.Wait()` for your task to complete before running `Geometry.Combine` – Rachel Sep 26 '12 at 14:19
  • I use Parallel.ForEach() to compute the data. But calling Combine *is* the background work, so I don't understand how using a task factory would help my problem. I need to call Geometry.Combine for a lot of combinations of Geometries to see if they intersect and what part does intersect (think 250000*250000 combinations in this case, which really is a very large number - lucky me the real number is lower due to a lot of optimization). Building the items is no problem at all, it is running Geometry.Combine which is the main workload. – Sam Sep 26 '12 at 14:36
  • @Sam Ok, I think I misread your question then... I thought you were having problems with your background calculations being run at a different time than `Geometry.Combine` and some action completing before another did. I still think your best bet would be to try and build a string of your data instead of combining geometries, but that depends on what format your data is in. – Rachel Sep 26 '12 at 14:46
  • yes, my problem is I need Geometry.Combine. And I can't run it on a string, so I don't see how building a data string would help me. I can't create my own version of "Combine", since intersecting two complex geometries is far beyond string management. – Sam Sep 26 '12 at 14:51
  • @Sam I'm not positive since I haven't worked with `Geometry.Combine`, but I think it would be the equivilant of something like looping through `PathGeometry.Figures` for each `PathGeometry`, doing a `switch` statement on the type of shape in the collection and building a string based on its data, combining all the data for `PathGeometry.Figures` to a single string for the `PathGeometry` object, then combining the two strings from two `PathGeometry` objects, taking care to move the `Z` to the end of the string if `IsClosed == true` – Rachel Sep 26 '12 at 15:21
  • @Sam See the edit to my answer for an idea of what I was thinking of – Rachel Sep 26 '12 at 15:35
  • 1
    ok, I think I see where the misunderstanding is: Combine is not used for adding both areas, but for finding the intersecting set. English is not my first language, so I don't know if this is the correct term. In this picture if my geometries are the circles it would be the red area I'm looking for: http://upload.wikimedia.org/wikipedia/commons/thumb/9/99/Venn0001.svg/150px-Venn0001.svg.png – Sam Sep 27 '12 at 09:07
  • Your code would be adding both shapes to each other, which is a very different operation of intersecting them. – Sam Sep 27 '12 at 09:17
  • 1
    @Sam Ahhhh I understand now, your picture really helped clarify things. You're right, I was misunderstanding exactly what `Geometry.Combine` did. I'm not sure I can help you with this then... I'm not that familiar with advanced geometry methods. – Rachel Sep 27 '12 at 11:30
  • Thats why I need some other class which offers a Combine-method like this and is not dependent on the GUI/Dispatcher. – Sam Sep 27 '12 at 12:04
3

System.Windows.Media also contains a class StreamGeometry, which is a "light-weight alternative" to PathGeometry. It does not support data binding, animation, or modification but since it derives from Geometry it should have a Combine method.

See StreamGeometry @ MSDN

Lars
  • 6,421
  • 1
  • 23
  • 24
0

I've only just had a play with it, but the NetTopologySuite looks like it'll do what you want.

The below code creates two polygons, checks whether they intersect, finds their area and calculates their intersection.

var gf = new GeometryFactory();

var c1 = new[] { new Coordinate(0, 0), new Coordinate(2, 0),
                 new Coordinate(2, 2), new Coordinate(0, 2),
                 new Coordinate(0, 0) };
var lr1 = gf.CreateLinearRing(c1);
var p1 = gf.CreatePolygon(lr1, new ILinearRing[0]);

var c2 = c1.Select(c => new Coordinate(c.X + 1, c.Y + 1)).ToArray();
var lr2 = gf.CreateLinearRing(c2);
var p2 = gf.CreatePolygon(lr2, new ILinearRing[0]);

var intersects = p1.Intersects(p2);        // true
var intersection = p1.Intersection(p2);    // another polygon
var area = intersection.Area;              // 1.0
Rawling
  • 49,248
  • 7
  • 89
  • 127