判断一个坐标点是否在不规则多边形内部的算法

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

在GIS(地理信息管理系统)中,判断一个坐标是否在多边形内部是个经常要遇到的问题。乍听起来还挺复杂。根据W. Randolph Franklin 提出的PNPoly算法,只需区区几行代码就解决了这个问题。

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

if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY) {// 这个测试都过不了。。。直接返回false;}

接下来是核心算法部分:

int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{int i, j, c = 0;for (i = 0, j = nvert-1; i < nvert; j = i++) {if ( ((verty[i]>testy) != (verty[j]>testy)) &&(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )c = !c;}return c;
}
ArgumentMeaning
nvertNumber of vertices in the polygon. Whether to repeat the first vertex at the end is discussed below.
vertx, vertyArrays containing the x- and y-coordinates of the polygon’s vertices.
testx, testyX- and y-coordinate of the test point.

额,代码就这么简单,但到底啥意思呢:

首先,参数nvert 代表多边形有几个点。浮点数testx, testy代表待测试点的横坐标和纵坐标,vertx,verty分别指向储存多边形横纵坐标数组的首地址。

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

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

verty[i] <testy < verty[j]

或者

verty[j] <testy < verty[i]

2.待测点test是否在i,j两点之间的连线之下?看不懂后半短if statement的朋友请自行在纸上写下i,j两点间的斜率公式,要用到一点初中解析几何和不等式的知识范畴,对广大码农来说小菜一碟。

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

可这到底是啥意思啊?

这个表达式的意思是说,随便画个多边形,随便定一个点,然后通过这个点水平划一条线,先数数看这条横线和多边形的边相交几次,(或者说先排除那些不相交的边,第一个判断条件),然后再数这条横线穿越多边形的次数是否为奇数,如果是奇数,那么该点在多边形内,如果是偶数,则在多边形外。详细的数学证明这里就不做了,不过读者可以自行画多边形进行验证。

判断一个点是否在多边形内部 - 射线法思路
比如说,我就随便涂了一个多边形和一个点,现在我要给出一种通用的方法来判断这个点是不是在多边形内部(别告诉我用肉眼观察……)。

这里写图片描述

首先想到的一个解法是从这个点做一条射线,计算它跟多边形边界的交点个数,如果交点个数为奇数,那么点在多边形内部,否则点在多边形外。

在这里插入图片描述

这个结论很简单,那它是怎么来的?下面就简单讲解一下。

首先,对于平面内任意闭合曲线,我们都可以直观地认为,曲线把平面分割成了内、外两部分,其中“内”就是我们所谓的多边形区域。

在这里插入图片描述

基于这一认识,对于平面内任意一条直线,我们可以得出下面这些结论:

直线穿越多边形边界时,有且只有两种情况:进入多边形或穿出多边形。
在不考虑非欧空间的情况下,直线不可能从内部再次进入多边形,或从外部再次穿出多边形,即连续两次穿越边界的情况必然成对。
直线可以无限延伸,而闭合曲线包围的区域是有限的,因此最后一次穿越多边形边界,一定是穿出多边形,到达外部。
在这里插入图片描述

现在回到我们最初的题目。假如我们从一个给定的点做射线,还可以得出下面两条结论:

如果点在多边形内部,射线第一次穿越边界一定是穿出多边形。
如果点在多边形外部,射线第一次穿越边界一定是进入多边形。
在这里插入图片描述

把上面这些结论综合起来,我们可以归纳出:

当射线穿越多边形边界的次数为偶数时,所有第偶数次(包括最后一次)穿越都是穿出,因此所有第奇数次(包括第一次)穿越为穿入,由此可推断点在多边形外部。
在这里插入图片描述

当射线穿越多边形边界的次数为奇数时,所有第奇数次(包括第一次和最后一次)穿越都是穿出,由此可推断点在多边形内部。

在这里插入图片描述

到这里,我们已经了解了这个解法的思路,大家可以试着自己写一个实现出来。

不知道大家思考得怎么样,有没有遇到一些不好处理的特殊情况。今天就来讲讲射线法在实际应用中的一些问题和解决方案。

1点在多边形的边上

前面我们讲到,射线法的主要思路就是计算射线穿越多边形边界的次数。那么对于点在多边形的边上这种特殊情况,射线出发的这一次,是否应该算作穿越呢?

在这里插入图片描述

看了上面的图就会发现,不管算不算穿越,都会陷入两难的境地——同样落在多边形边上的点,可能会得到相反的结果。这显然是不正确的,因此对这种特殊情况需要特殊处理。

2点和多边形的顶点重合

在这里插入图片描述

这其实是第一种情况的一个特例。

3射线经过多边形顶点

射线刚好经过多边形顶点的时候,应该算一次还是两次穿越?这种情况比前两种复杂,也是实现中的难点,后面会讲解它的解决方案。

在这里插入图片描述

4射线刚好经过多边形的一条边

这是上一种情况的特例,也就是说,射线连续经过了多边形的两个相邻顶点。

在这里插入图片描述

解决方案:

1判断点是否在线上的方法有很多,比较简单直接的就是计算点与两个多边形顶点的连线斜率是否相等,中学数学都学过。

2点和多边形顶点重合的情况更简单,直接比较点的坐标就行了。

3顶点穿越看似棘手,其实我们换一个角度,思路会大不相同。先来回答一个问题,射线穿越一条线段需要什么前提条件?没错,就是线段两个端点分别在射线两侧。只要想通这一点,顶点穿越就迎刃而解了。这样一来,我们只需要规定被射线穿越的点都算作其中一侧。

在这里插入图片描述

如上图,假如我们规定射线经过的点都属于射线以上的一侧,显然点D和发生顶点穿越的点C都位于射线Y的同一侧,所以射线Y其实并没有穿越CD这条边。而点C和点B则分别位于射线Y的两侧,所以射线Y和BC发生了穿越,由此我们可以断定点Y在多边形内。同理,射线X分别与AD和CD都发生了穿越,因此点X在多边形外,而射线Z没有和多边形发生穿越,点Z位于多边形外。

解决了第三点,这一点就毫无难度了。根据上面的假设,射线连续经过的两个顶点显然都位于射线以上的一侧,因此这种情况看作没有发生穿越就可以了。由于第三点的解决方案实际上已经覆盖到这种特例,因此不需要再做特别的处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/555304.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cos7安装mysql_centos7 安装 mysql

centos7 安装 mysql1. 卸载MariaDB, centos 7 自带的[rootesbu-cos7-01 /]# rpm -qa|grep -i mariadb用命令卸载 rpm -e --nodeps2. 官网下载https://dev.mysql.com/downloads/mysql/5.7.html#downloads3. 解压 tar -xvf#tar -xvf mysql-5.7.22-1.el7.x86_64.rpm-bundle由于是…

MySQL中LIMIT的使用方法

一、任务描述 搜索表结构中的某些部分的数据&#xff0c;比如&#xff0c;最后面三个&#xff0c;最前面三个&#xff0c;第2到8条记录&#xff0c;等等。 二、实战演练 1、 select * from tablename order by orderfield desc/asc limit position&#xff0c; counter&#…

mysql++多版本安装_MySQL多版本多实例安装启动

多版本&#xff0c;大版本不同测试多实例&#xff0c;一个MySQL5.7.30一个MySQL8.0.20解压8.0tar -xvf mysql-8.0.20-linux-glibc2.12-x86_64.tartar -xJf mysql-8.0.20-linux-glibc2.12-x86_64.tar.xz改名移动/mysql8.0.20mv mysql-8.0.20-linux-glibc2.12-x86_64 /mysql8.0.2…

MySQL数据库查询优化技巧

1.对查询进行优化&#xff0c;应尽量避免全表扫描&#xff0c;首先应考虑在 where 及 order by 涉及的列上建立索引。 2.应尽量避免在 where 子句中对字段进行 null 值判断&#xff0c;否则将导致引擎放弃使用索引而进行全表扫描&#xff0c;如&#xff1a; select id from t…

cheungssh mysql密码_CheungSSH安装及基本使用

CheungSSH比Ansible的使用更简单&#xff0c;尤其是配置方面&#xff01;而Ansible有的功能&#xff0c; 我的这个程序一样有&#xff0c;和Ansible一样是python开发&#xff0c; 所以跟Ansible的模式一样&#xff0c; 但是CheungSSH 操作更简单&#xff01;配置更轻量&#xf…

MySQL100万条数据的一张表,如何查询优化?

1.两种查询引擎查询速度&#xff08;myIsam 引擎 &#xff09; InnoDB 中不保存表的具体行数&#xff0c;也就是说&#xff0c;执行select count(*) from table时&#xff0c;InnoDB要扫描一遍整个表来计算有多少行。 MyISAM只要简单的读出保存好的行数即可。 注意的是&…

mysql增删改查操作语句_学习笔记-mysql增删改查操作语句

1.mysql增删改查操作语句&#xff1a;1)如果想在一个已经建好的表中添加一列&#xff0c;可以用以下代码&#xff1a;alter table t1 add column addr varchar(20) not null;2)这条语句会向已有的表t1中加入一列&#xff0c;这一列在表的最后一列位置。如果我们希望添加在指定的…

mybatis group by 分组查询:将返回结果封装为map

文章目录1. 最简单但性能最差的做法2. 使用group by分组查询&#xff0c;将查询结果封装成类3.group by分组查询&#xff0c;将结果封装为map。直接封装为map&#xff1f;List1. 最简单但性能最差的做法 在逻辑层分多次对数据库进行查询。伪代码如下。 List<String> na…

esl证明函 oracle_强弱分明 Astralis证明之战—ESL科隆B组浅析

HLTV排名前16名中的13支队伍(缺席的是G2和North)将齐聚这座坐落在莱茵河畔的城市进行厮杀&#xff0c;争夺比赛的最高荣誉。这项赛事也是继今年卡托维茨Major以来含金量最高&#xff0c;强队参赛最多的线下赛事。下面我们就来分析一下B组的出线形势。B组分组*本次比赛的赛制依旧…

关于new ArrayList()和Collections.emptyList()

很明显 new ArrayList()是创建一个Collection实例&#xff0c;它是Collection集合下面的一个实现类&#xff08;中间继承了AbstractList&#xff09;&#xff0c;它的实例有Collection的增加&#xff0c;删除&#xff0c;修改等方法&#xff0c; ArrayList平常用的很多&#x…

mysql 1418错误_mysql 1418错误原因及解决

摘自 http://blog.sina.com.cn/s/blog_6f68845001013k8a.html具体错误&#xff1a;使用mysql创建、调用存储过程&#xff0c;函数以及触发器的时候会有错误符号为1418错误。ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL,or READS SQL DATA in its dec…

ArrayList()和Collections.emptyList()的区别emptyList()、emptySet()、emptyMap()的作用和好处以及要注意的地方

前言 Java中ArrayList或许是我们平时开发最常用的一个集合类了&#xff0c;其次是HashMap&#xff0c;基本上满足了业务开发的绝大多数场景。今天要说的就是Collections.emptyList()和new ArrayList()的区别以及注意事项。 先来一段代码 运行main方法&#xff0c;会有如下输出…

mysql 5.6加用户_Mysql 5.6添加修改用户名和密码的方法

先登录MySQLshell> mysql --userroot mysql有密码的需要添加 –password 或-p 选项添加用户mysql>CREATE USER finleylocalhost IDENTIFIED BY some_pass;mysql>GRANT ALL PRIVILEGES ON *.* TO finleylocalhost WITH GRANT OPTION;mysql>CREATE USER finley% IDEN…

java中SimpleDateFormat线程安全问题及解决方案

最近看到一篇文章提到了SimpleDateFormat这个类。说这个类在单线程程序中没问题&#xff0c;但是在多线程环境下会线程安全的问题。 出于兴趣对这个问题进行了查证。网上有很多关于这个问题的文章&#xff0c;也解析了其中的原因。主要原因是因为SimpleDateFormat转换日期是通…

基于mysql搭建框架环境搭建_Maven+Spring+Spring MVC+MyBatis+MySQL,搭建SSM框架环境

项目建设完成之后的结构&#xff1a;数据库的表结构如下&#xff1a;环境建设&#xff1a;搭建Maven环境、Tomcat环境、需要MySql 数据库支持&#xff0c;使用的编程工具Eclipse (这些是前期准备)&#xff1b;开始创建工程&#xff1a;1.创建一个Maven工程&#xff1a;选择weba…

DataIntegrityViolationException: Error attempting to get column处理方案汇总

项目背景 项目整体采用的是springbootmybatis 方式。有一次做数据查询的时候。console突然报&#xff1a;DataIntegrityViolationException: Error attempting to get column ‘xx’…异常。起初没在意。以为是xml中的SQL写错了&#xff0c;排查了没问题。百度一下这个报错&…

mysql 查询商品列表 显示tag_让前台页面商品列表显示后台数据库中的商品

品牌&#xff1a;经销商&#xff1a;规格&#xff1a;每零售价&#xff1a;元购买日期&#xff1a;件&#xffe5;19.6&#xffe5;19.6删除实现单选&#xff0c;全选商品function checkAll(obj, name){var el document.getElementsByTagName(input);var len el.length;for (…

servlet的本质是什么,它是如何工作的?

servlet的本质是什么&#xff0c;它是如何工作的&#xff1f;基于计算机和网络通讯(主要是http协议)构建网络应用的&#xff0c;具体的实现细节是怎样的? 查看https://www.zhihu.com/question/21416727

oschina mysql limit_如何提高MySQL Limit查询的性能

在MySQL数据库操作中&#xff0c;我们在做一些查询的时候总希望能避免数据库引擎做全表扫描&#xff0c;因为全表扫描时间长&#xff0c;而且其中大部分扫描对客户端而言是没有意义的。其实我们可以使用Limit关键字来避免全表扫描的情况&#xff0c;从而提高效率。有个几千万条…

Mybatis原理:结果集封装详解

​ 经过sql参数解析、sql动态组装和执行sql&#xff0c;相对而言&#xff0c;结果集的封装&#xff0c;是mybatis数据处理的最后一环。这里只对查询结果而言&#xff0c;因为更新语句一般都是返回影响的行数。抛开mybatis&#xff0c;如果让我们组装结果&#xff0c;我们该如何…