Не-GUI альтернатива PathGeometry?

У меня есть две огромные (>100000 элементов) коллекции PathGeometry, которые мне нужно сравнить с помощью PathGeometry.Combine примерно так:

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()
    }
  }

К моему удивлению, PathGeometry является частью графического интерфейса и использует диспетчер, что иногда вызывает проблемы, поскольку мои вычисления выполняются в некотором фоновом потоке без графического интерфейса с помощью Parallel.ForEach().

Поэтому я ищу альтернативу PathGeometry, которая не связана с GUI.
Мои цифры довольно сложны и добавляют много PathFigures к PathGeometry.Figures.

Я сам создаю эти PathGeometries из некоторых раздутых правительственных XML-файлов, поэтому было бы без проблем создать что-то еще. Но мне нужна функция, чтобы создать пересечение двух из этих геометрий (не добавляя их друг к другу), чтобы получить область, которую покрывают обе геометрии, например красную область на этой диаграмме:

3 ответа

Решение

System.Windows.Media также содержит класс StreamGeometry, которая является "легкой альтернативой" PathGeometry, Он не поддерживает привязку данных, анимацию или модификацию, но так как он происходит от Geometry это должно иметь Combine метод.

См. StreamGeometry @ MSDN

Как насчет разбора вашего Path.Data в строку, используя геометрию "мини-язык" вместо использования PathGeometry объект?

Например, вместо создания чего-то вроде этого:

<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>

Вы могли бы создать

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

Или в вашем случае XAML, вероятно, будет выглядеть примерно так:

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

Path.Data - это строка, которая должна быть в определенном формате, и более короткий способ рисования геометрии вашего пути.

Вот список того, что означают буквы, и какие параметры ожидаются, взятые по ссылке выше:

  • Значение F - устанавливает свойство Geometry.FillRule. Используйте 0 для EvenOdd или 1 для NonZero. Эта команда должна появиться в начале строки (если вы решите ее использовать).

  • M x,y - Создает новую PathFigure для геометрии и устанавливает ее начальную точку. Эта команда должна использоваться перед любыми другими командами, кроме F. Однако вы также можете использовать ее во время рисования, чтобы переместить начало вашей системы координат. (М обозначает движение).

  • L x,y - создает сегмент LineSegment до указанной точки.

  • H x - Создает горизонтальный LineSegment, используя указанное значение X и сохраняя значение Y постоянным.

  • V y - создает вертикальный сегмент LineSegment, используя указанное значение Y и сохраняя значение X постоянным.

  • Радиус x, радиус Y, градусы isLargeArch, isClockwise x,y - Создает ArcSegment в указанную точку. Вы указываете радиусы эллипса, описывающего дугу, количество градусов, на которое поворачивается дуга, и логические флаги, которые устанавливают свойства IsLargeArc и SweepDirection.

  • C x1, y1 x2, y2 x,y - Создает сегмент Безье к указанной точке, используя контрольные точки в точках (x1, y1) и (x2, y2).

  • Q x1, y1 x,y - создает сегмент QuadraticBezierSegment для указанной точки с одной контрольной точкой в ​​точке (x1, y1).

  • S x2, y2 x,y - Создает плавный BezierSegment, используя вторую контрольную точку из предыдущего BezierSegment в качестве первой контрольной точки в новом BezierSegment.

  • Z - Завершает текущий PathFigure и устанавливает IsClosed в true. Вам не нужно использовать эту команду, если вы не хотите устанавливать IsClosed в true - вместо этого просто используйте M, если вы хотите начать новый PathFigure или завершить строку.

Например, строка, использованная в примере выше (M 10 100 L 100 100 L 100 50 Z) можно разбить на:

  • M 10 100 - Стартовый путь в 10 100
  • L 100 100 - Нарисуй линию до 100 100
  • L 100 50 - Нарисуй линию до 100,50
  • Z - конец строки

В вашем случае, когда ваш Path.Data читается из базы данных, вы бы начали с создания строки, содержащей M x y где x y это начальная позиция x,y для пути, а затем читайте ваши сегменты по одному и добавляйте их в строку, используя сокращение выше, а затем заканчивайте строку Z

Обратите внимание, что заглавные и строчные буквы имеют разные значения. Заглавная буква означает, что вы указываете абсолютное значение для вашего сегмента пути, а строчная буква означает, что оно должно быть относительно последнего сегмента.

Например, если ваш сегмент говорит "L 10 10", это означает нарисовать линию в позиции 10,10 на сетке, в то время как "l 10 10" означает нарисовать линию 10 вверх и 10 справа от текущей позиции.

В вашем случае, когда ваш PathGeometry считывается из базы данных, вам нужно будет преобразовать каждый PathGeometry в строку, а затем объединить строки.

Вот пример грубого кода. Я знаю, что это не работает, но, надеюсь, это может указать вам верное направление

редактировать

На основании вашего отредактированного вопроса, звучит так, как будто ваш элемент данных хранится как PathGeometryтак что вам может понадобиться конвертировать ваши PathGeometry объекты в строку, а затем просто объединить строки

Вот очень грубый пример. Я вполне уверен, что у меня неправильный синтаксис и, возможно, немного логики, так как я не очень знаком с PathGeometry объект, но, надеюсь, он может указать вам в правильном направлении

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();
}

Я только что поиграл с этим, но NetTopologySuite выглядит так, как будто он будет делать то, что вы хотите.

Приведенный ниже код создает два многоугольника, проверяет, пересекаются ли они, находит их площадь и вычисляет их пересечение.

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
Другие вопросы по тегам