计算几何

时间:2021-08-12
本文章向大家介绍计算几何,主要包括计算几何使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

点、线段、多边形

计算几何

点积

向量的内积(点乘/数量积)

\[a=[a_1,a_2,...a_n] \quad b=[b_1,b_2,....,b_n]\\ a\cdot b=a_1b_1+a_2b_2+....a_nb_n \]

注意:点乘的结果是一个标量

a·b = |a||b|cos∠(a, b)a ,b正交,则 a.b=0

内积的几何意义

  • 表征或计算两个向量之间的夹角
  • b向量在a向量方向上的投影
def inner_prod(point1, point2):
    # 计算 向量内积
    # point1 (x,y)
    # point2 (x,y)
    # x1*x2+y1*y2+...

    return point1[0] * point2[0] + point1[1] * point2[1]

叉积

向量的外积,又称叉积,是向量代数(解析几何)中的一个概念

向量叉乘(行列式计算):向量a(x1,y1),向量b(x2,y2) axb=A||B|Sin(θ)

a^b=x1y2-x2y1

若结果大于0,表示向量b在向量a的逆时针方向;若等于0,表示向量a与向量b平行。**

(顺逆时针是指两向量平移至起点相连,从某个方向旋转到另一个向量小于180度)

计算矢量叉积是与直线和线段相关算法的核心部分

  • 向量的叉积的模表示这两个向量围成的平行四边形的面积
  • a×b(×为向量叉乘),若结果小于0,表示向量b在向量a的顺时针方向
  • axb 结果大于0,表示向量b在向量a的逆时针方向
  • 若结果等于 0 则说明 a b 共线可能同向,也可能反向
double PointOffLine( Point pt, Point start, Point end )
{
return ((pt.x - end.x)*(start.y - end.y)-(start.x - end.x)*(pt.y - end.y))*1.;
}

def cross(p0, p1, p2):
	"""
	向量a×向量b   (×为向量叉乘)
	向量 a (p1x-p0x,p1y-p0y) p1->p0    向量 b (p2x-p0x,p2y-p0y) p2->p0
	:param p1: 起始点 (x,y)
	:param p2: 终点1  (x,y)
	:param p3: 终点2  (x,y)
	:return:
	"""
	# 计算叉积
	# 若结果小于0,表示向量b在向量a的顺时针方向;
	# 若结果大于0,表示向量b在向量a的逆时针方向;
	# 若等于0,表示向量a与向量b 方向重合

	x1 = p1[0] - p0[0]
	y1 = p1[1] - p0[1]
	x2 = p2[0] - p0[0]
	y2 = p2[1] - p0[1]
	return x1 * y2 - x2 * y1

点与点距离

计算平方根

点到直线的距离

  • 方法一、通过点到直线的公式

过点P向线段AB上画垂线,判断垂足有没有落在线段上。如果落在线段上,ok,距离就是垂线段的长度;如果没有,则距离转化为点到线段两端点的距离。

原理

求解直线方程

\[\frac{y_2-y_1}{x_2-x_1}=\frac{y_2-y_1}{x-x_1} \]
\[A=y_2-y_1\\ B=x_1-x_2\\ C=x_1*(y_1-y_2) \]

点到直线的距离公式

\[\frac{|A*x0+B*y0+C|}{\sqrt{A^2+B^2}} \]
def get_distance_from_point_to_line(point, line_point1, line_point2):
    """
    :param point:           点坐标  (x,y)
    :param line_point1:     线段坐标1 (x,y)
    :param line_point2:     线段坐标2 (x,y)
    :return: 
    """
    # 对于两点坐标为同一点时,返回点与点的距离
    if line_point1 == line_point2:
        point_array = np.array(point)
        point1_array = np.array(line_point1)
        return np.linalg.norm(point_array - point1_array)
    # 计算直线的三个参数
    A = line_point2[1] - line_point1[1]
    B = line_point1[0] - line_point2[0]
    C = (line_point1[1] - line_point2[1]) * line_point1[0] + \
        (line_point2[0] - line_point1[0]) * line_point1[1]
    # 根据点到直线的距离公式计算距离
    distance = np.abs(A * point[0] + B * point[1] + C) / (np.sqrt(A ** 2 + B ** 2))
    return distance

点到线段的最小距离

  • 方法二、向量法、判断为位置
  • 先计算AD
\[\overrightarrow{AD}=\frac{\overrightarrow{AP}.\overrightarrow{AB}}{|\overrightarrow{AB}|^2}.|\overrightarrow{AB}|=\frac{\overrightarrow{AP}.\overrightarrow{AB}}{|\overrightarrow{AB}|}.\frac{|\overrightarrow{AB}|}{|\overrightarrow{AB}|} \]

当AD计算结束后,可以根据AD的相应的坐标值得到D的坐标,在计算CD的长度

判断投影点D的位置

\[r=\frac{\overrightarrow{AP}.\overrightarrow{AB}}{|\overrightarrow{AB}|^2} \]
如果情况是上图1所示,那么0<r<1;
图2所示的情况,那么r ≥1;
图3所示的情况,那么得到r ≤0;

根据r不同,最短距离也不同

\[d=\begin{cases} |\overrightarrow{AP}|, r<=0\\ |\overrightarrow{BP}|, r>=1\\|\overrightarrow{DP}|, 0<r<1 \end{cases} \]
double PointToSegDist(double x, double y, double x1, double y1, double x2, double y2)
{
    
double cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1);
if (cross <= 0) return Math.Sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
  
double d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
if (cross >= d2) return Math.Sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
  
double r = cross / d2;
double px = x1 + (x2 - x1) * r;
double py = y1 + (y2 - y1) * r;
return Math.Sqrt((x - px) * (x - px) + (py - y) * (py - y));
}


def inner_prod(point1, point2):
    # 计算 向量内积
    # point1 (x,y)
    # point2 (x,y)
    # x1*x2+y1*y2+...

    return point1[0] * point2[0] + point1[1] * point2[1]


def pointToSegDist(point, l1, l2):
    # 计算点到线段的最小距离 (注意是线段)
    # point (x,y)
    # l1    (x,y)
    # l2    (x,y)
    # r ≤0,         最小长度为 l1point
    # 0 < r < 1;    最小长度为 l2point
    # r ≥1;         point在 l1l2 上的投影

    import math

    x, y = l2[0] - l1[0], l2[1] - l1[1]
    l1l2 = [x, y]
    x, y = point[0] - l1[0], point[1] - l1[1]
    l1point = [x, y]
    cross = inner_prod(l1l2, l1point)       # 计算内积(向量l1l2,向量l1point)
    if (cross <= 0):
        return math.sqrt((point[0] - l1[0]) ** 2 + (point[1] - l1[1]) ** 2)
    d2 = (l2[0] - l1[0]) ** 2 + (l2[1] - l1[1]) ** 2
    if cross >= d2:
        return math.sqrt((point[0] - l2[0]) ** 2 + (point[1] - l2[1]) ** 2)

    r = cross / d2
    px = l1[0] + (l2[0] - l1[0]) * r
    py = l1[1] + (l2[1] - l1[1]) * r

    return np.sqrt((point[0] - px) ** 2 + (point[1] - py) ** 2)


point = [5, -5]
l1 = [0, 0]
l2 = [5, 0]
res = pointToSegDist(point, l1, l2)
print(res)

参考博客: https://blog.csdn.net/angelazy/article/details/38489293

点在线段上

方法1,计算点到线段的最小距离

点与线段的关系点与线段只有两种关系

  • 在线段上面
  • 在线段外面

方法2

判断方法(满足两个条件)

  • 点P与线段AB任意端点间的斜率与AB线段斜率相等,点P在直线AB上
  • 确保给定点P的Y坐标在线段AB两个端点的Y坐标之间, 确保在线段AB上

注意:当点P为AB端点时,不存在斜率,直接在线段上

def point_isonline(point, l1, l2):
    D = 0
    if point == l1 or point == l2:
        return 1
    try:
        # 防止出现斜率不存在的情况
        slope_pointl1 = round((l1[1] - point[1]) / (l1[0] - point[0]), 7)
        slope_pointl2 = round((l2[1] - point[1]) / (l2[0] - point[0]), 7)

        if slope_pointl1 == slope_pointl2 != 0:
            if l1[0] < point[0] < l2[0] or l1[0] > point[0] > point[0]:
                D = 1
            else:
                D = 0
        elif slope_pointl1 == slope_pointl2 == 0:
            if l1[1] < point[1] < l2[1] or l1[1] > point[1] > point[1]:
                D = 1
            else:
                D = 0
        else:
            D = 0
    except:
        # 斜率不存在
        if point[0] == l1[0] == l2[0] and l1[1] < point[1] < l2[1] or l1[1] > point[1] > point[1]:
            D = 1
        else:
            D = 0
    return D

# point = [0, 5]
# l1 = [0, 0]
# l2 = [5, 5]
# res = point_isonline(point, l1, l2)
# print(res)

线段和线段

假设有两条线段AB,CD,若AB,CD相交,我们可以确定:

1.线段AB与CD所在的直线相交,即点A和点B分别在直线CD的两边;

2.线段CD与AB所在的直线相交,即点C和点D分别在直线AB的两边;

同时满足是两线段相交的充要条件,可以证明线段AB与CD相交

针对向量AB,CD 相交的冲要条件:

AB×AC > 0;向量AD在向量AB的顺势针方向,AB×AD < 0,两叉乘结果异号

特殊情况

  • AB CD 共线
  • AB CD 共点(AB CD 共点、或者其中一点在 另一段线段上面)

AB×AD=0, 或者 AB×AC = 0

def segment(l1, l2, p3, p4):
    '''
    :param l1,l2:  线段AB 的起点和终点 l1(x,y) l2(x,y)
    :param p3,p4:  线段CD 的起点和终点 p3(x,y) p4(x,y)
    :return:
    '''
    # 判断两线段是否相交
    # 依次判断CD在AB的两侧,AB在CD的两侧, 需要叉乘的结果异号
    cross_l1l2_l1p3 = cross(l1, l2, p3)
    cross_l1l2_l1p4 = cross(l1, l2, p4)

    cross_p3p4_p3l1 = cross(p3, p4, l1)
    cross_p3p4_p3l2 = cross(p3, p4, l2)

    if cross_l1l2_l1p3 * cross_l1l2_l1p4 <= 0 and cross_p3p4_p3l1 * cross_p3p4_p3l2 <= 0:
        D = 1
    else:
        D = 0
    return D


def cross(p0, p1, p2):
    """
    向量a×向量b   (×为向量叉乘)
    向量 a (p1x-p0x,p1y-p0y) p1->p0    向量 b (p2x-p0x,p2y-p0y) p2->p0
    :param p0: 起始点
    :param p1: 终点1
    :param p2: 终点2
    :return:
    """
    # 计算叉积
    # 若结果小于0,表示向量b在向量a的顺时针方向;
    # 若结果大于0,表示向量b在向量a的逆时针方向;
    # 若等于0,表示向量a与向量b 方向重合

    x1 = p1[0] - p0[0]
    y1 = p1[1] - p0[1]
    x2 = p2[0] - p0[0]
    y2 = p2[1] - p0[1]
    return x1 * y2 - x2 * y1

l1=[0,0]
l2=[5,0]
p3=[2,-2]
p4=[2,2]
res=segment(l1, l2, p3, p4)
print(res)

参考文献:https://www.cnblogs.com/tuyang1129/p/9390376.html

点与多边形关系

判断点是否处于多边形内方法

方法一

叉乘判别法(只适用于凸多边形)

将凸多边形中每一个边AB,与被测点P,求PA×PB判断结果的符号是否发生变化,如果没有变化,P在多边形内;反之点处于凸多边形外。

def cross(p0, p1, p2):
    """
    向量a×向量b   (×为向量叉乘)
    向量 a (p1x-p0x,p1y-p0y) p1->p0    向量 b (p2x-p0x,p2y-p0y) p2->p0
    :param p0: 起始点
    :param p1: 终点1
    :param p2: 终点2
    :return:
    """
    # 计算叉积
    # 若结果小于0,表示向量b在向量a的顺时针方向;
    # 若结果大于0,表示向量b在向量a的逆时针方向;
    # 若等于0,表示向量a与向量b 方向重合

    x1 = p1[0] - p0[0]
    y1 = p1[1] - p0[1]
    x2 = p2[0] - p0[0]
    y2 = p2[1] - p0[1]
    return x1 * y2 - x2 * y1

def point_isin_poly(l1, l2, x1y1, x2y2, x3y3, x4y4):
    # 检测点 是否在多边形内部
    # 计算内部的一点和每个多边形 线段的叉积 判断叉积的符号是否相同
    # 叉积=0在多边形边界上
    cross1 = cross(l1, x1y1, x2y2)
    cross2 = cross(l1, x2y2, x3y3)
    cross3 = cross(l1, x3y3, x4y4)
    cross4 = cross(l1, x4y4, x1y1)
    print(cross1,cross2,cross3,cross4)
    if (cross1 >= 0 and cross2 >= 0 and cross3 >= 0 and cross4 >= 0) or (
            cross1 <= 0 and cross2 <= 0 and cross3 <= 0 and cross4 <= 0):
        D = 1
    else:
        D = 0
    return D

# l1=[5,1.5]
# l2=[5,0]
# x1y1, x2y2, x3y3, x4y4=[0,0],[0,2],[8,2],[8,0]
# res=point_isin_poly(l1, l2, x1y1, x2y2, x3y3, x4y4)
# print(res)

线段与多边形的关系

class Line_Polygon_Relations():
    def __init__(self):

        pass

    def check_line_poly(self, l1, l2, x1y1, x2y2, x3y3, x4y4):
        '''
        :param l1,l2:  线段 L 的 坐标
        :param x1y1:   多边形 x1234 的坐标 坐标 顺时针方向旋转
        :param x2y2:
        :param x3y3:
        :param x4y4:
        :return:
        '''

        D = 0

        pxy12 = self.segment(l1, l2, x1y1, x2y2)  # 判断线段是否和 多边形线段相交
        pxy23 = self.segment(l1, l2, x2y2, x3y3)
        pxy34 = self.segment(l1, l2, x3y3, x4y4)
        pxy41 = self.segment(l1, l2, x4y4, x1y1)

        # 只能判断是 线段和多边形是否相交,如果在多边形内部需要再次判断
        if pxy12 or pxy23 or pxy34 or pxy41:
            D = 1
        else:

            # 是否在多边形内部判断
            # 计算内部的一点和每个多边形 线段的叉积 判断叉积的符号是否相同
            cross1 = self.cross(l1, x1y1, x2y2)
            cross2 = self.cross(l1, x2y2, x3y3)
            cross3 = self.cross(l1, x3y3, x4y4)
            cross4 = self.cross(l1, x4y4, x1y1)

            if (cross1 > 0 and cross2 > 0 and cross3 > 0 and cross4 > 0) or (
                    cross1 < 0 and cross2 < 0 and cross3 < 0 and cross4 < 0):
                D = 1
            else:
                D = 0
        return D

    def segment(self, l1, l2, p3, p4):
        '''
        :param l1,l2:  线段AB 的起点和终点 l1(x,y) l2(x,y)
        :param p3,p4:  线段CD 的起点和终点 p3(x,y) p4(x,y)
        :return:
        '''
        # 判断两线段是否相交
        # 依次判断CD在AB的两侧,AB在CD的两侧
        cross_l1p3_l2p3 = self.cross(l1, l2, p3)
        cross_l1p4_l2p4 = self.cross(l1, l2, p4)
        cross_p3l1_p4l1 = self.cross(p3, p4, l1)
        cross_p3l2_p3l2 = self.cross(p3, p4, l2)

        if cross_l1p3_l2p3 * cross_l1p4_l2p4 <= 0 and cross_p3l1_p4l1 * cross_p3l2_p3l2 <= 0:
            D = 1
        else:
            D = 0

        return D

    def cross(self, p0, p1, p2):
        """

        向量a×向量b   (×为向量叉乘)
        向量 a (p1x-p0x,p1y-p0y) p1->p0    向量 b (p2x-p0x,p2y-p0y) p2->p0
        :param p1:
        :param p2:
        :param p3:
        :return:
        """
        # 计算叉积
        # 若结果小于0,表示向量b在向量a的顺时针方向;
        # 若结果大于0,表示向量b在向量a的逆时针方向;
        # 若等于0,表示向量a与向量b 方向重合

        x1 = p1[0] - p0[0]
        y1 = p1[1] - p0[1]
        x2 = p2[0] - p0[0]
        y2 = p2[1] - p0[1]
        return x1 * y2 - x2 * y1



line_poly=Line_Polygon_Relations()
res = line_poly.check_line_poly((5, 4), (10, 2), (0, 0), (10, 10), (20, 5), (10, 0))

原文地址:https://www.cnblogs.com/tian777/p/15131857.html