【问题标题】:Detecting coincident subset of two coincident line segments检测两个重合线段的重合子集
【发布时间】:2011-01-16 09:13:22
【问题描述】:

这个问题与:

但是请注意,一个有趣的子问题在大多数解决方案中被完全掩盖了,即使存在三个子案例,它们也只为重合案例返回 null:

  • 重合但不重叠
  • 触摸点和重合
  • 重叠/重合线子段

例如,我们可以设计一个这样的 C# 函数:

public static PointF[] Intersection(PointF a1, PointF a2, PointF b1, PointF b2)

其中 (a1,a2) 是一条线段,(b1,b2) 是另一条线段。

此功能需要涵盖大多数实现或解释所掩盖的所有奇怪情况。为了解释重合线的怪异,该函数可以返回一个 PointF 数组:

  • 如果线平行或不相交(无限条线相交但线段不相交,或者线平行),则结果点为零(或null)李>
  • 如果它们相交或如果它们在某一点重合,则为一个结果点(包含相交位置)
  • 如果两条线重合,则两个结果点(用于线段的重叠部分)

【问题讨论】:

  • 我意识到这个问题只是被问到,所以你可以发布你的答案。您应该将其标记为已接受的答案。 FWIW,在问题中使用较少对抗性的语言也没有什么坏处。
  • @tfinniga:直到我重写它并让它听起来像一个谜题而不是一个要求时,我才意识到它是对抗性的。我的目标不是让其他人为我做这项工作,而是证明没有其他实现甚至有效。 (如果你能证明我错了并找到了一个非常好的解决方案(现在就在 SO 上),我很乐意给你 100 代表)。
  • 谢谢,我觉得这样好多了。针对这种常见需求的防弹实施很有价值,而且重新表述的问题更令人愉快。

标签: c# .net graphics geometry gdi+


【解决方案1】:
    // port of this JavaScript code with some changes:
    //   http://www.kevlindev.com/gui/math/intersection/Intersection.js
    // found here:
    //   http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/563240#563240

public class Intersector
{
    static double MyEpsilon = 0.00001;

    private static float[] OverlapIntervals(float ub1, float ub2)
    {
        float l = Math.Min(ub1, ub2);
        float r = Math.Max(ub1, ub2);
        float A = Math.Max(0, l);
        float B = Math.Min(1, r);
        if (A > B) // no intersection
            return new float[] { };
        else if (A == B)
            return new float[] { A };
        else // if (A < B)
            return new float[] { A, B };
    }

    // IMPORTANT: a1 and a2 cannot be the same, e.g. a1--a2 is a true segment, not a point
    // b1/b2 may be the same (b1--b2 is a point)
    private static PointF[] OneD_Intersection(PointF a1, PointF a2, PointF b1, PointF b2)
    {
        //float ua1 = 0.0f; // by definition
        //float ua2 = 1.0f; // by definition
        float ub1, ub2;

        float denomx = a2.X - a1.X;
        float denomy = a2.Y - a1.Y;

        if (Math.Abs(denomx) > Math.Abs(denomy))
        {
            ub1 = (b1.X - a1.X) / denomx;
            ub2 = (b2.X - a1.X) / denomx;
        }
        else
        {
            ub1 = (b1.Y - a1.Y) / denomy;
            ub2 = (b2.Y - a1.Y) / denomy;
        }

        List<PointF> ret = new List<PointF>();
        float[] interval = OverlapIntervals(ub1, ub2);
        foreach (float f in interval)
        {
            float x = a2.X * f + a1.X * (1.0f - f);
            float y = a2.Y * f + a1.Y * (1.0f - f);
            PointF p = new PointF(x, y);
            ret.Add(p);
        }
        return ret.ToArray();
    }

    private static bool PointOnLine(PointF p, PointF a1, PointF a2)
    {
        float dummyU = 0.0f;
        double d = DistFromSeg(p, a1, a2, MyEpsilon, ref dummyU);
        return d < MyEpsilon;
    }

    private static double DistFromSeg(PointF p, PointF q0, PointF q1, double radius, ref float u)
    {
        // formula here:
        //http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
        // where x0,y0 = p
        //       x1,y1 = q0
        //       x2,y2 = q1
        double dx21 = q1.X - q0.X;
        double dy21 = q1.Y - q0.Y;
        double dx10 = q0.X - p.X;
        double dy10 = q0.Y - p.Y;
        double segLength = Math.Sqrt(dx21 * dx21 + dy21 * dy21);
        if (segLength < MyEpsilon)
            throw new Exception("Expected line segment, not point.");
        double num = Math.Abs(dx21 * dy10 - dx10 * dy21);
        double d = num / segLength;
        return d;
    }

    // this is the general case. Really really general
    public static PointF[] Intersection(PointF a1, PointF a2, PointF b1, PointF b2)
    {
        if (a1.Equals(a2) && b1.Equals(b2))
        {
            // both "segments" are points, return either point
            if (a1.Equals(b1))
                return new PointF[] { a1 };
            else // both "segments" are different points, return empty set
                return new PointF[] { };
        }
        else if (b1.Equals(b2)) // b is a point, a is a segment
        {
            if (PointOnLine(b1, a1, a2))
                return new PointF[] { b1 };
            else
                return new PointF[] { };
        }
        else if (a1.Equals(a2)) // a is a point, b is a segment
        {
            if (PointOnLine(a1, b1, b2))
                return new PointF[] { a1 };
            else
                return new PointF[] { };
        }

        // at this point we know both a and b are actual segments

        float ua_t = (b2.X - b1.X) * (a1.Y - b1.Y) - (b2.Y - b1.Y) * (a1.X - b1.X);
        float ub_t = (a2.X - a1.X) * (a1.Y - b1.Y) - (a2.Y - a1.Y) * (a1.X - b1.X);
        float u_b = (b2.Y - b1.Y) * (a2.X - a1.X) - (b2.X - b1.X) * (a2.Y - a1.Y);

        // Infinite lines intersect somewhere
        if (!(-MyEpsilon < u_b && u_b < MyEpsilon))   // e.g. u_b != 0.0
        {
            float ua = ua_t / u_b;
            float ub = ub_t / u_b;
            if (0.0f <= ua && ua <= 1.0f && 0.0f <= ub && ub <= 1.0f)
            {
                // Intersection
                return new PointF[] {
                    new PointF(a1.X + ua * (a2.X - a1.X),
                        a1.Y + ua * (a2.Y - a1.Y)) };
            }
            else
            {
                // No Intersection
                return new PointF[] { };
            }
        }
        else // lines (not just segments) are parallel or the same line
        {
            // Coincident
            // find the common overlapping section of the lines
            // first find the distance (squared) from one point (a1) to each point
            if ((-MyEpsilon < ua_t && ua_t < MyEpsilon)
               || (-MyEpsilon < ub_t && ub_t < MyEpsilon))
            {
                if (a1.Equals(a2)) // danger!
                    return OneD_Intersection(b1, b2, a1, a2);
                else // safe
                    return OneD_Intersection(a1, a2, b1, b2);
            }
            else
            {
                // Parallel
                return new PointF[] { };
            }
        }
    }


}

这里是测试代码:

    public class IntersectTest
    {
        public static void PrintPoints(PointF[] pf)
        {
            if (pf == null || pf.Length < 1)
                System.Console.WriteLine("Doesn't intersect");
            else if (pf.Length == 1)
            {
                System.Console.WriteLine(pf[0]);
            }
            else if (pf.Length == 2)
            {
                System.Console.WriteLine(pf[0] + " -- " + pf[1]);
            }
        }

        public static void TestIntersect(PointF a1, PointF a2, PointF b1, PointF b2)
        {
            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("Does      " + a1 + " -- " + a2);
            System.Console.WriteLine("intersect " + b1 + " -- " + b2 + " and if so, where?");
            System.Console.WriteLine("");
            PointF[] result = Intersect.Intersection(a1, a2, b1, b2);
            PrintPoints(result);
        }

        public static void Main()
        {
            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("line segments intersect");
            TestIntersect(new PointF(0, 0),
                          new PointF(100, 100),
                          new PointF(100, 0),
                          new PointF(0, 100));
            TestIntersect(new PointF(5, 17),
                          new PointF(100, 100),
                          new PointF(100, 29),
                          new PointF(8, 100));
            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("");

            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("just touching points and lines cross");
            TestIntersect(new PointF(0, 0),
                          new PointF(25, 25),
                          new PointF(25, 25),
                          new PointF(100, 75));
            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("");

            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("parallel");
            TestIntersect(new PointF(0, 0),
                          new PointF(0, 100),
                          new PointF(100, 0),
                          new PointF(100, 100));
            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("");

            System.Console.WriteLine("----");
            System.Console.WriteLine("lines cross but segments don't intersect");
            TestIntersect(new PointF(50, 50),
                          new PointF(100, 100),
                          new PointF(0, 25),
                          new PointF(25, 0));
            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("");

            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("coincident but do not overlap!");
            TestIntersect(new PointF(0, 0),
                          new PointF(25, 25),
                          new PointF(75, 75),
                          new PointF(100, 100));
            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("");

            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("touching points and coincident!");
            TestIntersect(new PointF(0, 0),
                          new PointF(25, 25),
                          new PointF(25, 25),
                          new PointF(100, 100));
            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("");

            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("overlap/coincident");
            TestIntersect(new PointF(0, 0),
                          new PointF(75, 75),
                          new PointF(25, 25),
                          new PointF(100, 100));
            TestIntersect(new PointF(0, 0),
                          new PointF(100, 100),
                          new PointF(0, 0),
                          new PointF(100, 100));
            System.Console.WriteLine("----------------------------------------------------------");
            System.Console.WriteLine("");

            while (!System.Console.KeyAvailable) { }
        }

    }

这是输出:

-------------------------------------------------- -------- 线段相交 -------------------------------------------------- -------- {X=0, Y=0} - {X=100, Y=100} intersect {X=100, Y=0} -- {X=0, Y=100} 如果是,在哪里? {X=50, Y=50} -------------------------------------------------- -------- {X=5, Y=17} - {X=100, Y=100} intersect {X=100, Y=29} -- {X=8, Y=100} 如果是,在哪里? {X=56.85001, Y=62.30054} -------------------------------------------------- -------- -------------------------------------------------- -------- 只是接触点和线交叉 -------------------------------------------------- -------- {X=0, Y=0} - {X=25, Y=25} intersect {X=25, Y=25} -- {X=100, Y=75} 如果是,在哪里? {X=25, Y=25} -------------------------------------------------- -------- -------------------------------------------------- -------- 平行线 -------------------------------------------------- -------- {X=0, Y=0} - {X=0, Y=100} intersect {X=100, Y=0} -- {X=100, Y=100} 如果是,在哪里? 不相交 -------------------------------------------------- -------- ---- 线交叉但线段不相交 -------------------------------------------------- -------- {X=50, Y=50} - {X=100, Y=100} intersect {X=0, Y=25} -- {X=25, Y=0} 如果是,在哪里? 不相交 -------------------------------------------------- -------- -------------------------------------------------- -------- 重合但不重叠! -------------------------------------------------- -------- {X=0, Y=0} - {X=25, Y=25} intersect {X=75, Y=75} -- {X=100, Y=100} 如果是,在哪里? 不相交 -------------------------------------------------- -------- -------------------------------------------------- -------- 感人之处又不谋而合! -------------------------------------------------- -------- {X=0, Y=0} - {X=25, Y=25} intersect {X=25, Y=25} -- {X=100, Y=100} 如果是,在哪里? {X=25, Y=25} -------------------------------------------------- -------- -------------------------------------------------- -------- 重叠/重合 -------------------------------------------------- -------- {X=0, Y=0} - {X=75, Y=75} intersect {X=25, Y=25} -- {X=100, Y=100} 如果是,在哪里? {X=25, Y=25} -- {X=75, Y=75} -------------------------------------------------- -------- {X=0, Y=0} - {X=100, Y=100} intersect {X=0, Y=0} -- {X=100, Y=100} 如果是,在哪里? {X=0, Y=0} -- {X=100, Y=100} -------------------------------------------------- --------

【讨论】:

  • errr... 我没有注意到你也发布了这个问题 =P。我已经删除了反对票。
  • ...或者不是,有人告诉我,我的 10 分钟旧帖子太旧了,无法更改。我已经投票赞成你的另一个答案来弥补它。对不起。 :)
  • 感谢“收回”。您会注意到,我还将问题标记为社区 Wiki,以避免人们认为我是代表种田等。不过,这是一个有趣的问题……正在发布一个违背网站精神的工作代码示例?也许我应该将它发布到其他地方(博客等)并链接到它?关键是其他类似问题的许多答案都存在致命缺陷,这确实……也违背了网站的精神。感谢您的尝试解释。也许我应该在完成后将其发布在某个地方的博客上。对不起...
  • 另外,既然是社区 Wiki,我们都没有失去任何代表!
  • 您好,非常好,非常有帮助,但仍然存在一个错误。在 pointOfLine 中,计算的距离检查点是否在线上,而不是线段中。 Il 线段为 (0,0)->(10,0) 点为 (15, 0),则到线段的距离为 0,pointOfLine 为真
【解决方案2】:

这真的很简单。如果你有两条线,你可以找到两个形式为 y = mx + b 的方程。例如:

y = 2x + 5
y = x - 3

所以,当 y1 = y2 在同一个 x 坐标时,两条线相交,所以...

2x + 5 = x - 3 
x + 5 = -3
x = -8

当x=-8 y1=y2 并且你已经找到了交点。这应该很容易翻译成代码。如果没有交点比每条线的斜率m相等,那么你甚至不需要进行计算。

【讨论】:

  • 这也是一个微妙的错误:当点在彼此之上和之下时,斜率是无限的,所有的地狱都失败了。
  • 当每条线的斜率相等时,它们仍然可以相交于一点或线段,甚至完全不重叠。
【解决方案3】:

听起来您有自己的解决方案,这很棒。我有一些改进它的建议。

该方法有一个主要的可用性问题,因为它很难理解 (1) 输入的参数是什么意思,以及 (2) 出来的结果是什么意思。如果你想使用这个方法,这两个都是你必须弄清楚的小谜题。

我更倾向于使用类型系统来更清楚地说明这个方法的作用。

我首先定义一个类型——也许是一个结构,特别是如果它是不可变的——称为 LineSegment。 LineSegment 由两个表示终点的 PointF 结构组成。

其次,如果您需要表示作为两个或多个基因座联合的轨迹,我将定义一个抽象基类型“Locus”和派生类型 EmptyLocus、PointLocus、LineSegmentLocus 以及可能的 UnionLocus。空轨迹只是单例,点轨迹只是单点,以此类推。

现在您的方法签名变得更加清晰:

static Locus Intersect(LineSegment l1, LineSegment l2)

此方法采用两条线段并计算作为它们交点的点的轨迹——空的、单个点或线段。

请注意,您可以推广此方法。计算线段与线段的交点很棘手,但计算线段与点、点与点或任何与空轨迹的交点容易。并且不难将交叉点扩展到任意位点联合。因此,您实际上可以这样写:

static Locus Intersect(Locus l1, Locus l2)

嘿,现在很明显,Intersect 可能是轨迹上的扩展方法:

static Locus Intersect(this Locus l1, Locus l2)

添加从 PointF 到 PointLocus 和 LineSegment 到 LineSegmentLocus 的隐式转换,你可以这样说

var point = new PointF(whatever);
var lineseg = new LineSegment(somepoint, someotherpoint);
var intersection = lineseg.Intersect(point);
if (intersection is EmptyLocus) ...

使用好类型系统可以大大提高程序的可读性。

【讨论】:

  • 感谢您的建议和扩展。
  • 这是一个很棒的方法 Eric,我以前使用枚举与其他对象结合来提供结果。这是优雅和优越的。谢谢。
【解决方案4】:

@Jared,好问题和好答案。

如 Joseph O' Rourke 的 CGA 常见问题解答 here 中所述,可以通过将点沿线的位置表示为单个参数的函数来简化问题。

令 r 为表示 P 的参数 沿包含 AB 的线的位置, 含义如下:

      r=0      P = A
      r=1      P = B
      r<0      P is on the backward extension of AB
      r>1      P is on the forward extension of AB
      0<r<1    P is interior to AB

沿着这些思路思考,对于任何点 C(cx,cy),我们计算 r 如下:

double deltax = bx - ax;
double deltay = by - ay;
double l2 = deltax * deltax + deltay * deltay;
double r = ((ay - cy) * (ay - by) - (ax - cx) * (bx - ax)) / l2;

这应该更容易计算重叠段。

请注意,我们避免取平方根,因为只需要长度的平方。

【讨论】:

  • 链接参考的加号。对我有用
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-02-11
  • 2018-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多