算法 - PNPoly解决点到多边形的距离问题

最近做了一个算法题【盒马配货】:

(题目大意)盒马店的配送范围由一些点组成的多边形确定,给定一个点判断其是否在配送范围内,若在,则此点不需要挪动,打印"no 0";若不在,则给出此点需要挪动到配送范围的最短距离,打印"yes 距离"。

如何求解点到多边形的距离

此题求解需要解决两个问题:

  • 点到多边形的边的最短距离。
  • 点是否包含在多边形内。

点到边的距离

计算点到多边形最短距离的基本原理是:依次计算点到多边形每条边的距离,然后筛选出最短距离。

image

如下图,假设AB为多边形的一条边,现在求点P到AB的距离。

image

根据向量内积公式(\vec a \cdot \vec b=|a||b|\cos\theta),我们可以推出:

image

根据以上公式,我们可以求出t,进而求出点D的坐标,最终PD的长度就很容易求得了。

但是还有一些边界条件需要注意,即最终D点不是落在AB上,有以下三种情况:

  • t < 0,D在BA延长线上,此时最短距离取PA;
  • 0 <= t <= 1,D在AB上,此时最短距离取PD;
  • t > 1,D在AB延长线上,此时最短距离取PB;
image

Java实现代码如下:

private static double pointToSegmentDist(double px, double py, double ax, double ay, double bx, double by) {
    double ABx = bx - ax;
    double ABy = by - ay;
    double APx = px - ax;
    double APy = py - ay;

    double AB_AP = ABx * APx + ABy * APy;
    double distAB2 = ABx * ABx + ABy * ABy;

    double Dx = ax, Dy = ay;
    if (distAB2 != 0) {
        double t = AB_AP / distAB2;
        if (t >= 1) {
            Dx = bx;
            Dy = by;
        } else if (t > 0) {
            Dx = ax + ABx * t;
            Dy = ay + ABy * t;
        } else {
            Dx = ax;
            Dy = ay;
        }
    }

    double PDx = Dx - px, PDy = Dy - py;

    return Math.sqrt(PDx * PDx + PDy * PDy);
}

点是否包含在多边形内

根据W. Randolph Franklin 提出的PNPoly算法,只需区区几行代码就解决了这个问题。

假设配送范围多边形的点横纵坐标分别存放在两个数组xs、ys里,(x,y)表示配送点的坐标,先贴代码:

private static void polygon(double[] xs, double[] ys, double x, double y) {
    boolean contained = false; // 点是否包含在多边形内

    double xMin = Arrays.stream(xs).min().getAsDouble();
    double xMax = Arrays.stream(xs).max().getAsDouble();
    double yMin = Arrays.stream(ys).min().getAsDouble();
    double yMax = Arrays.stream(ys).max().getAsDouble();

    if (x > xMax || x < xMin || y > yMax || y < yMin) {
        contained = false;
    }
    // 核心算法部分
    int N = xs.length;
    double dist = Double.MAX_VALUE;
    for (int i = 0, j = N - 1; i < N; j = i++) {
        if (((ys[j] > y) != (ys[i] > y))
            && (x < (xs[j] - xs[i]) * (y - ys[i]) / (ys[j] - ys[i]) + xs[i])) {
            contained = !contained;
        }
        dist = Math.min(dist, pointToSegmentDist(x, y, xs[i], ys[i], xs[j], ys[j]));
    }
    System.out.println(contained ? "no 0" : "yes" + " " + dist);
}

首先,我们需要取得该数组在横坐标和纵坐标的最大值和最小值,根据这四个点算出一个四边型,判断目标坐标点是否在这个四边型之内,如果在这个四边型之外,那可以跳过后面较为复杂的计算,直接返回false。

if (x > xMax || x < xMin || y > yMax || y < yMin) {
    contained = false;
}

接下来是核心算法部分:

for (int i = 0, j = N - 1; i < N; j = i++) {
    if (((ys[j] > y) != (ys[i] > y))
        && (x < (xs[j] - xs[i]) * (y - ys[i]) / (ys[j] - ys[i]) + xs[i])) {
        contained = !contained;
    }
}

每次计算都涉及到相邻的两个点和待测试点,然后考虑两个问题:

  • 被测试点的纵坐标testy是否在本次循环所测试的两个相邻点纵坐标范围之内,即

    ys[i] <y < ys[j]

    或者

    ys[j] <y < ys[i]。

  • 待测点test是否在i,j两点之间的连线之下(相交判断)。

每次这两个条件同时满足的时候我们把返回的布尔量取反

这个表达式的意思是说,随便画个多边形,随便定一个点,然后通过这个点水平划一条线,先数数看这条横线和多边形的边相交几次(可先排除那些不相交的边,即第一个判断条件),然后再数这条横线穿越多边形的次数是否为奇数,如果是奇数,那么该点在多边形内,如果是偶数,则在多边形外(射线法)。

点在直线下 - 相交判断

如下图,ab与过p点的水平线相交于c,

image

则有:

image

Java代码实现:

if (((ys[j] > y) != (ys[i] > y))
    && (x < (xs[j] - xs[i]) * (y - ys[i]) / (ys[j] - ys[i]) + xs[i])) {
    contained = !contained;
}

点在多边形内部 - 射线法

判断点是否在多边形内,可以从这个点做一条射线,计算它跟多边形边界的交点个数,如果交点个数为奇数,那么点在多边形内部,否则点在多边形外。参考https://www.cnblogs.com/anningwang/p/7581545.html

参考

https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html

https://www.cnblogs.com/anningwang/p/7581545.html

https://jingsam.github.io/2016/09/26/polydist.html

(完)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,572评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,071评论 3 414
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,409评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,569评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,360评论 6 404
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,895评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,979评论 3 440
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,123评论 0 286
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,643评论 1 333
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,559评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,742评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,250评论 5 356
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,981评论 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,363评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,622评论 1 280
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,354评论 3 390
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,707评论 2 370