【问题标题】:Line Segments Intersection(intersection Point)线段交点(交点)
【发布时间】:2015-07-03 10:44:01
【问题描述】:

我创建了一个函数来计算两条线段的交点。

不幸的是,如果其中一个段是垂直的,则下面的代码将不起作用

    public static Point intersection(Segment s1, Segment s2) {
    double x1 = s1.getP1().getX();
    double y1 = s1.getP1().getY() ;
    double x2 = s1.getP2().getX();
    double y2 = s1.getP2().getY() ;
    double x3 = s2.getP1().getX();
    double y3 = s2.getP1().getY();
    double x4 = s2.getP2().getX();
    double y4 = s2.getP2().getY();

    double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
    if (d == 0) {
        return null;
    }
    double xi = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
    double yi = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
    Point p = new Point(xi, yi);
    if (xi < Math.min(x1, x2) || xi > Math.max(x1, x2)) {
        return null;
    }
    if (xi < Math.min(x3, x4) || xi > Math.max(x3, x4)) {
        return null;
    }
    return p;
}

当我有一条垂直线段时的问题,这个公式

double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);

等于0,方法返回null。

我该如何处理这个异常。

谢谢

【问题讨论】:

  • 虽然您的问题具有可读性和可回答性,但仍有一些需要改进的地方,包括解释每个非显而易见的变量以及包括您迄今为止尝试过的内容。您最终会得到答案,但请同时查看stackoverflow.com/help/how-to-ask
  • 先检查竖排情况再单独处理即可。
  • 还有其他公式可以处理这种情况吗?我想要做的是向其中一个坐标添加一些微值,所以我不会乘以 0,然后我会得到交点,但会失去一些精度
  • 这个公式不关心垂直段。当只有一个段是垂直的时,d 不为零。当段平行时 d 等于 0 - 将此视为特殊情况。

标签: java geometry line line-intersection


【解决方案1】:

无特殊情况的线交点

来自射影几何的背景,我会用齐次坐标写点:

v1 = [x1, y1, 1]
v2 = [x2, y2, 1]
v3 = [x3, y3, 1]
v4 = [x4, y4, 1]

那么连接两点的线和两条线的交点都可以用叉积表示:

[x5, y5, z5] = (v1 × v2) × (v3 × v4)

您可以对其进行去均质化以找到结果点

[x5/z5, y5/z5]

无需处理任何特殊情况。如果你的线是平行的,最后一点会导致除以零,所以你可能想抓住这种情况。

对细分市场的限制

以上是针对无限线的。如果交点落在边界框之外,您可能希望保留返回 null 的代码。但是,如果您想要真正的线段,则该代码是不正确的:您可能有一个交点,位于其中一个线段之外但仍在边界框内。

可以使用方向检查谓词来实现正确的检查。如果它们形成的三角形具有一个方向,则上面给出的三个向量vi 的行列式将具有正号,而对于相反方向则具有负号。所以点v3v4 位于s1 if 的不同边

det(v1, v2, v3) * det(v1, v2, v4) < 0

同样v1v2 位于s2 if 的不同边

det(v3, v4, v1) * det(v3, v4, v2) < 0

因此,如果这两个都满足,则您在段之间有一个交集。如果要包含分段端点,请将这些不等式中的 &lt; 更改为

【讨论】:

  • 这是一个很好的概括。挑剔:为了限制段,您还需要考虑一个段的端点位于另一个段的特殊情况。请参阅第 3 版算法简介,Corman 等人,第 1017 页。
【解决方案2】:

我已经尝试在没有测试的情况下对其进行编码......我希望它可以工作! ^^

public static Point intersection(Segment s1, Segment s2) {
    // Components of the first segment's rect.
    Point v1 = new Point(s1.p2.x - s1.p1.x, s1.p2.y - s1.p1.y); // Directional vector
    double a1 = v.y;
    double b1 = -v.x;
    double c1 = v1.x * s1.p1.y - s1.v.y * s1.p1.x;

    // Components of the second segment's rect.
    Point v2 = new Point(s2.p2.x - s2.p1.x, s2.p2.y - s2.p1.y);
    double a2 = v2.y;
    double b2 = -v2.x;
    double c2 = v2.x * s2.p1.y - s2.v.y * s2.p1.x;

    // Calc intersection between RECTS.
    Point intersection = null;
    double det = a1 * b2 - b1 * a2;
    if (det != 0) {
        intersection = new Point(
            (b2 * (-c1) - b1 * (-c2)) / det;
            (a1 * (-c2) - a2 * (-c1)) / det;
        );
    }

    // Checks ff segments collides.
    if (
        intersection != null &&
        (
            (s1.p1.x <= intersection.x && intersection.x <= s1.p2.x) ||
            (s1.p2.x <= intersection.x && intersection.x <= s1.p1.x)
        ) &&
        (
            (s1.p1.y <= intersection.y && intersection.y <= s1.p2.y) ||
            (s1.p2.y <= intersection.y && intersection.y <= s1.p1.y)
        ) &&
        (
            (s2.p1.x <= intersection.x && intersection.x <= s2.p2.x) ||
            (s2.p2.x <= intersection.x && intersection.x <= s2.p1.x)
        ) &&
        (
            (s2.p1.y <= intersection.y && intersection.y <= s2.p2.y) ||
            (s2.p2.y <= intersection.y && intersection.y <= s2.p1.y)
        )
    )
        return intersection;

    return null;
};

【讨论】:

    【解决方案3】:

    这是我的答案。我已经通过创建一个循环来测试它是否准确,该循环检查它给出的答案是否与 Boost 几何库给出的答案相同,并且他们同意每个测试,尽管我在下面写的那个比它快得多Boost中的那个。对于所有可能的线段对,该测试会生成所有可能的线段,其中 x 是 [-3,2] 中的整数,y 是 [-3,2] 中的整数。

    下面的代码认为在端点处相交的线段是相交的。 T 形交叉口也被认为是交叉口。代码是 c++,但很容易适应任何语言。它基于不同的 stackoverflow 答案,但该答案没有正确处理端点。

    它使用叉积方法,可以报告一个点是在给定射线的左边还是右边。

    在数学方面需要进行一些优化,但与使用 g++ -O2 编译相比,性能并没有提高,有时甚至会降低性能!编译器能够进行这些优化,所以我更愿意让代码保持可读性。

    // is_left(): tests if a point is Left|On|Right of an infinite line.
    //    Input:  three points p0, p1, and p2
    //    Return: >0 for p2 left of the line through p0 and p1
    //            =0 for p2 on the line
    //            <0 for p2 right of the line
    //    See: Algorithm 1 "Area of Triangles and Polygons"
    //    This is p0p1 cross p0p2.
    extern inline coordinate_type_fp is_left(point_type_fp p0, point_type_fp p1, point_type_fp p2) {
      return ((p1.x() - p0.x()) * (p2.y() - p0.y()) -
              (p2.x() - p0.x()) * (p1.y() - p0.y()));
    }
    
    // Is x between a and b, where a can be lesser or greater than b.  If
    // x == a or x == b, also returns true. */
    extern inline coordinate_type_fp is_between(coordinate_type_fp a,
                                                coordinate_type_fp x,
                                                coordinate_type_fp b) {
      return x == a || x == b || (a-x>0) == (x-b>0);
    }
    
    // https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
    extern inline bool is_intersecting(const point_type_fp& p0, const point_type_fp& p1,
                                       const point_type_fp& p2, const point_type_fp& p3) {
      const coordinate_type_fp left012 = is_left(p0, p1, p2);
      const coordinate_type_fp left013 = is_left(p0, p1, p3);
      const coordinate_type_fp left230 = is_left(p2, p3, p0);
      const coordinate_type_fp left231 = is_left(p2, p3, p1);
    
      if (p0 != p1) {
        if (left012 == 0) {
          if (is_between(p0.x(), p2.x(), p1.x()) &&
              is_between(p0.y(), p2.y(), p1.y())) {
            return true; // p2 is on the line p0 to p1
          }
        }
        if (left013 == 0) {
          if (is_between(p0.x(), p3.x(), p1.x()) &&
              is_between(p0.y(), p3.y(), p1.y())) {
            return true; // p3 is on the line p0 to p1
          }
        }
      }
      if (p2 != p3) {
        if (left230 == 0) {
          if (is_between(p2.x(), p0.x(), p3.x()) &&
              is_between(p2.y(), p0.y(), p3.y())) {
            return true; // p0 is on the line p2 to p3
          }
        }
        if (left231 == 0) {
          if (is_between(p2.x(), p1.x(), p3.x()) &&
              is_between(p2.y(), p1.y(), p3.y())) {
            return true; // p1 is on the line p2 to p3
          }
        }
      }
      if ((left012 > 0) == (left013 > 0) ||
          (left230 > 0) == (left231 > 0)) {
        if (p1 == p2) {
          return true;
        }
        return false;
      } else {
        return true;
      }
    }
    

    测试代码:

    BOOST_AUTO_TEST_CASE(small, *boost::unit_test::disabled()) {
      for (double x0 = -3; x0 < 3; x0++) {
        for (double y0 = -3; y0 < 3; y0++) {
          for (double x1 = -3; x1 < 3; x1++) {
            for (double y1 = -3; y1 < 3; y1++) {
              for (double x2 = -3; x2 < 3; x2++) {
                for (double y2 = -3; y2 < 3; y2++) {
                  for (double x3 = -3; x3 < 3; x3++) {
                    for (double y3 = -3; y3 < 3; y3++) {
                      point_type_fp p0{x0, y0};
                      point_type_fp p1{x1, y1};
                      point_type_fp p2{x2, y2};
                      point_type_fp p3{x3, y3};
                      linestring_type_fp ls0{p0,p1};
                      linestring_type_fp ls1{p2,p3};
                      BOOST_TEST_INFO("intersection: " << bg::wkt(ls0) << " " << bg::wkt(ls1));
                      BOOST_CHECK_EQUAL(
                          path_finding::is_intersecting(p0, p1, p2, p3),
                          bg::intersects(ls0, ls1));
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2016-12-22
      • 1970-01-01
      • 2015-05-17
      • 2010-10-24
      • 2012-05-26
      • 1970-01-01
      • 1970-01-01
      • 2012-10-14
      相关资源
      最近更新 更多