Вычислить пересечение двух ребер - PullRequest
0 голосов
/ 01 апреля 2009

У меня есть этот большой граф ребер и вершин в 2D-пространстве. Большой граф возвращается функцией, вычисленной в библиотеке C ++. Я читаю этот график и использую его для вычисления всех пересечений его ребер (сегментов линий). Я использую алгоритм развертки. Для обнаружения пересечения двух ребер у меня есть несколько проблем. У меня есть 4 различных метода, в соответствии с которыми я проверяю, пересекаются ли два ребра, и, если они положительные, я вычисляю и сохраняю их пересечение:

  1. Тот, который проверяет, являются ли два ребра диагоналями многоугольника. то есть координаты ребер одной линии, вставленные в уравнение другая строка имеет разные знаки.

  2. Тот, который вычисляет пересечение каждый раз и проверяет, найденное пересечение находится между конечными точками обоих сегментов.

  3. Тот, который является кодом эта ссылка реализована на C ++, хотя.

  4. Тот, который реализует первый метод, предложенный Джейсоном Коэном в этот вопрос .

"Проблема сводится к этому вопросу: пересекаются ли две линии от А до В и от С до D? Тогда вы можете задать это четыре раза (между линией и каждой из четырех сторон прямоугольника).

Вот векторная математика для этого. Я предполагаю, что линия от A до B является рассматриваемой линией, а линия от C до D является одной из прямоугольных линий. Моя запись такова, что Ax - это «x-координата A», а Cy - «y-координата C.» А «*» означает скалярное произведение, например ::1027

A*B = Ax*Bx + Ay*By.
E = B-A = ( Bx-Ax, By-Ay )
F = D-C = ( Dx-Cx, Dy-Cy ) 
P = ( -Ey, Ex )
h = ( (A-C) * P ) / ( F * P )

Этот номер h является ключом. Если h находится между 0 и 1, линии пересекаются, иначе они не пересекаются. Если F P равно нулю, вы, конечно, не можете сделать расчет, но в этом случае линии параллельны и поэтому пересекаются только в очевидных случаях. Точная точка пересечения C + F ч. Если h равно 0 или 1, линии касаются в конечной точке. Вы можете считать это «пересечением» или нет, как считаете нужным.

Для данных, которые я создал (небольшие данные с двойными значениями), я получил хорошие результаты со всеми 4 реализованными методами. Когда я использую любой из этих методов, реализованных в C ++, для данных из большого графа, я каждый раз получаю разные результаты и не очень хорошие результаты:

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

  2. Я всегда получаю 0 пересечений, несмотря ни на что.

  3. Я получаю намного больше пересечений, чем в 1.

  4. Для некоторого примера я получаю точки, которых нет на графике (так что даже нет пересечения). Но для некоторых примеров я получаю точки пересечения плюс некоторые другие точки.

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

Ответы [ 6 ]

2 голосов
/ 02 апреля 2009

Вам нужны юнит-тесты для ваших 4-х методов и для их проверки тщательно . В частности, при пересечении отрезков имеется много конечных случаев, таких как параллельные уклоны, совпадающие конечные точки, полностью или частично перекрывающиеся, в дополнение ко всем обычным проблемам допуска (например, что именно означает наклон "значит?).

Не разбивая вещи на более мелкие, более тестируемые модули, вам будет сложно сузить их.

0 голосов
/ 26 июня 2017

Только что вы сказали: алгоритм развертки ... Я использовал усиленный алгоритм Майерса 1985 года, в то время этот алгоритм был лучшим.

Что я сделал: работал с целым числом - то есть исправил пред. - числа, чтобы иметь дело с проблемами точности.

В качестве алгоритма пересечения двух отрезков: Первые очевидные тесты для диапазонов и если сегменты параллельны. Затем я вычислил ожидаемую точку пересечения в два раза и угол двух сегментов. Всякий раз, когда угол между двумя сегментами был «малым», я вычислял расстояние от точки пересечения, где расстояние двух отрезков линии становится больше 1 в моем целочисленном пространстве. Затем я разбил каждый из двух линейных сегментов на 3, как> ------- <с общей частью, так сказать, расширяя точку пересечения до отдельного сегмента, и удалил первые два сегмента. Я не беспокоился о том, что нижележащие полигоны стали вогнутыми. </p>

Поскольку сегменты были вычислены для вычисления графа деления многоугольников, вместо двух почти параллельных сегментов вместо двух почти параллельных сегментов было только 3 сегмента с более здоровыми углами.

Повышение также содержало распределенную сортировку слиянием точек события (в настоящее время: такой алгоритм очень параллелизуем !!!), тем самым уменьшая сложность самой сортировки из ожидаемого O (n log n) и ожидаемого O (n) , все же O (n log n) наихудший случай. Я настоятельно рекомендую пересмотреть алгоритм «однопотоковый» для некоторых методов «разделяй и властвуй» с его распараллеливанием.

0 голосов
/ 26 января 2010

Возможно, вы захотите попробовать Boost.Geometry (она же библиотека общей геометрии) .

0 голосов
/ 02 апреля 2009

Зачем изобретать велосипед?

Если вы можете справиться с опорой на Boost , я бы проверил библиотеку GTL в песочнице . Инструкции о том, как это скачать, читайте wiki (инструкции прямо на главной странице).

Библиотека GTL пригодна для любой реализации вашей структуры данных (т. Е. Она разработана в духе STL, где структуры данных и алгоритмы ортогональны). Код быстрый и правильный. Проверьте это, если можете.

0 голосов
/ 02 апреля 2009

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

Это код для 4-го метода (который пока дает мне лучшие результаты, но не все пересечения, но, по крайней мере, некоторое хорошее пересечение по сравнению с другими):

//4 method
double* QSweep::findIntersection(edge_t edge1,edge_t edge2) {  
    point_t p1=myPoints_[edge1[0]];
    point_t p2=myPoints_[edge1[1]];
    point_t p3=myPoints_[edge2[0]];
    point_t p4=myPoints_[edge2[1]];

    double xD1,yD1,xD2,yD2,xD3,yD3,xP,yP,h,denom;
    double* pt=new double[3];

    // calculate differences  
    xD1=p2[0]-p1[0];  
    xD2=p4[0]-p3[0];  
    yD1=p2[1]-p1[1];  
    yD2=p4[1]-p3[1];  
    xD3=p1[0]-p3[0];  
    yD3=p1[1]-p3[1];    

    xP=-yD1;
    yP=xD1;
    denom=xD2*(-yD1)+yD2*xD1;
    if (denom==0) {
        return NULL;
    }
    else{
    h=(xD3*(-yD1)+yD3*xD1)/denom;
    }
    std::cout<<"h is"<<h<<endl;
    if ((h>0)&&(h<1)){
        pt[0]=p3[0]+xD2*h;  
        pt[1]=p3[1]+yD2*h;
        pt[2]=0.00;
    }
    else{
        return NULL;
    }

    // return the valid intersection  
    return pt;  
}

void QSweep:: intersection(){
    nbIntersections=0;
    double* vector;
    for (int i=2;i<myEdges_.size()-1;i++){
        vector=findIntersection(myEdges_[i-1],myEdges_[i]);
        if (vector!=NULL){
                nbIntersections++;
                myIntersections.push_back(vector);
                swap(myEdges_[i-1], myEdges_[i]);
        }
    }
}

Функция пересечения всегда одна и та же, поэтому я просто нахожу функцию findIntersection.

Для метода 1 и 2 я использую следующую версию функции findIntersection:

double* QSweep::computeIntersection(double m1, double b1, double m2, double b2){
    double* v=new double[3];

    v[0]= (b2-b1)/(m1-m2);
    v[1]= (m1*b2-m2*b1)/(m1-m2);
    v[2]=0.00;
    return v;
    }

    double* QSweep::findIntersection(edge_t edge1, edge_t edge2){


    //equation for the support line of the first edge

    double a=myPoints_[edge1[0]][0];
    double b=myPoints_[edge1[0]][1];
    double c=myPoints_[edge1[1]][0];
    double d=myPoints_[edge1[1]][1];
    double m1=getSlope(a,b,c,d);
    double b1=getYIntercept(a,b,c,d);

    double x=myPoints_[edge2[0]][0];
    double y=myPoints_[edge2[0]][1];
    double s=myPoints_[edge2[1]][0];
    double t=myPoints_[edge2[1]][1];
    double m2=getSlope(x,y,s,t);
    double b2=getYIntercept(x,y,s,t);
    double* vector;

    double line2InLine1=evalEqnLineD(myPoints_[edge2[0]],edge1);
    double line2InLine1New=evalEqnLineD(myPoints_[edge2[1]],edge1);
    double line1InLine2=evalEqnLineD(myPoints_[edge1[0]],edge2);
    double line1InLine2New=evalEqnLineD(myPoints_[edge1[1]],edge2);

    if (m1==m2){
        return NULL;
    }
    else{

            if ((line1InLine2*line1InLine2New)<0 &&   (line2InLine1*line2InLine1New)<0){
            vector=computeIntersection(m1,b1,m2,b2);

            if ((vector[0]>a && vector[0]<c)&&(vector[0]>x && vector[0]<s)){
                return vector;
            }
            else{
                return NULL;
            }
        }
        else{
            return NULL;
        }
    }
    return vector;
}

Я начну снова с самого начала, чтобы увидеть, что общего у точек пересечения, которые я хочу, есть. Даже с некоторыми испытаниями я получаю не только хорошие точки пересечения, но и другие точки на графике, так что я действительно растерялся.

0 голосов
/ 02 апреля 2009

Хотя трудно сказать, не имея возможности увидеть ваш код, я подозреваю, что у вас возникают проблемы с надежностью ваших значений с плавающей запятой. Рассматривали ли вы использование целочисленных точек или какое-то улучшение надежности, например, удаление общих битов в значениях с плавающей запятой?

Существует геометрия с открытым исходным кодом GEOS (http://trac.osgeo.org/geos/)), которая может быть полезна для проверки набора данных. В GEOS также есть алгоритмы для выполнения мгновенного округления до целочисленной сетки и исключения общих битов для поможет вам определить, не столкнулись ли вы с проблемой устойчивости. Также стоит обратить внимание на то, как GEOS вычисляет пересечение, используя дуальность точечных линий в однородном пространстве (что я не могу точно сказать, является ли описанная вами техника проекции точечного произведения математически эквивалентной).

Кроме того, мое любимое решение для вычисления пересечений в графе ребер - использовать линию разметки в сочетании с монотонной цепью ребер. Когда вы используете монотонные цепочки, вы можете исключить множество тестов пересечения ребер, так как цепочки никогда не пересекаются. Это то, что делает GEOS.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...