【算法学习】射线法判断点在多边形内外(C#)以及确定内外两点连线与边界的交点
1.前言:
在GIS开发中,经常会遇到确定一个坐标点是否在一块区域的内部这一问题。
如果这个问题不是一个单纯的数学问题,例如:在判断DEM、二维图像像素点、3D点云点等含有自身特征信息的这些点是否在一个区域范围内部的时候,可以结合其高程信息、RGB信息、深度信息来辅助处理,相比与单纯从数学角度来看更简单、快速。
举几个我认为正确的例子:SLAM中前端角点的选取,利用的是OpenCV来提取;DEM提取边界,根据周围高程的有无;PS中扣出某物边界,利用的是RGB差异性;点云提取可以利用深度信息(本质也是RGB)来做。
但是,如果我现在只拥有点的坐标,该问题就变成一个数学问题了。
2.射线法:
该算法基本思路是从待定点朝任意方向射出一条射线(通常是水平向右),判断该射线与多边形边的交点个数。一般来说,交点个数为偶数(包括0),点在外部;交点个数为奇数,点在内部。
因为点和图形的位置是固定不动的,所以射线的朝向,对于最终的交点个数,也就是位置结果是没有影响的。
2.1 算法介绍
在分析前要先明白几个问题:
- 如果没有特殊需求,待求点在图形的边界(线段、交点)上,默认是属于图形内部的。
- 默认待求点的射线沿着x轴方向水平射出(水平向右)。
- 射线经过边界交点情况很常见,为了防止上一个线段的末顶点和下一个线段的首顶点(这两个是一个点)被算作两次,所以只看线段的y更小的一端,即参数方程的值域符号:[y1,y2)。
假如逆时针遍历各边,看下图示例:
(1)从简单情况开始分析:
最简单的情况当属一个规整的四边形,射线与四边形的交点个数存在的情况有:0,1,2。
如果,不考虑穿过顶点,不考虑点的射线与边平行(重合),就单纯考虑穿的全部是边,遇到这种情况:
先建立遍历边的参数方程,找到射线与参数方程的交点,再判断交点X交和待定点的位置关系。
如果P在X交左侧,有一个交点则计数+1;
如果P和X交是一个点,则说明P在边界线段上,直接返回true;
如果在X交右侧,没有交点。
注意:这里的上下yi和yj的取值,要包含下界,不包含上界,否则会被计算两次,而且这样可以有效忽略水平边界而待求点在左侧的问题。
当然,反过来也可以,都选择更大的y值。
(2)我认为的几种特殊情况,这几种特殊情况有特点:就是无法找到线段去做参数方程或者实际交点无数个。这几种特殊情况需要单独处理。
- 待求点就是边界交点:通过坐标判断,直接返回是否在边界内。
- 待求点在水平边界线上:通过坐标判断,直接返回是否在边界内。
- 待求点在水平边界线左侧:配合前后该水平边界先后线段参数方程的值域,这种情况可以直接忽略!(忽略不是没有考虑)
2.2 C#代码实现
using System;
using System.Collections.Generic;public class Point
{public double X { get; set; }public double Y { get; set; }public Point(double x, double y){X = x;Y = y;}
}
public class Polygon
{private List<Point> vertices;public Polygon(List<Point> points){vertices = points;}public bool IsPointInside(Point testPoint){int intersectionCount = 0;int vertexCount = vertices.Count;for (int i = 0, j = vertexCount - 1; i < vertexCount; j = i++){Point vi = vertices[i];Point vj = vertices[j];// 检查测试点是否在顶点上if ((vi.X == testPoint.X && vi.Y == testPoint.Y) ||(vj.X == testPoint.X && vj.Y == testPoint.Y)){return true;}// 检查测试点是否在水平边上if (vi.Y == vj.Y && vi.Y == testPoint.Y){if (testPoint.X > Math.Min(vi.X, vj.X) && testPoint.X < Math.Max(vi.X, vj.X)){return true;}}// 检查testpoint.y是否在两个端点的中间//if ((vi.Y > testPoint.Y) != (vj.Y > testPoint.Y)) // 这行代码更简单,但是有点小小的不直观if ((vi.Y <= testPoint.Y && vj.Y > testPoint.Y) || (vj.Y <= testPoint.Y && vi.Y > testPoint.Y)){double intersectionX = (vj.X - vi.X) * (testPoint.Y - vi.Y) / (vj.Y - vi.Y) + vi.X;// 处理边界情况if (testPoint.X == intersectionX){return true;}if (testPoint.X < intersectionX){intersectionCount++;}}}// 如果交点数为奇数,则点在多边形内部return intersectionCount % 2 != 0;}
}
internal class Program
{static void Main(string[] args){List<Point> vertices = new List<Point>{new Point(0,0),new Point(1,0),new Point(2,-1),new Point(3,0),new Point(5,0),new Point(5,1),new Point(4,1),new Point(4,2),new Point(3,3),new Point(3,4),new Point(2,4),new Point(2,3),new Point(1,3),new Point(1,4),new Point(0,4),new Point(0,2),new Point(-1,1),};Polygon polygon = new Polygon(vertices);List<Point> testPoint = new List<Point>();for (int i = -1; i < 6; i++){for (int j = -1; j < 7; j++){testPoint.Add(new Point(i, j));}}foreach (var p in testPoint){Console.WriteLine($"p点坐标:({p.X}, {p.Y}),是否在图形内部:{polygon.IsPointInside(p)}");}Console.ReadKey();}
}
用for循环写了一个从(-1,-1)到(5,6)覆盖的测试点,最后结果:
p点坐标:(-1, -1),是否在图形内部:False|p点坐标:(-1, 0),是否在图形内部:False
p点坐标:(-1, 1),是否在图形内部:True|p点坐标:(-1, 2),是否在图形内部:False
p点坐标:(-1, 3),是否在图形内部:False|p点坐标:(-1, 4),是否在图形内部:False
p点坐标:(-1, 5),是否在图形内部:False|p点坐标:(-1, 6),是否在图形内部:False
p点坐标:(0, -1),是否在图形内部:False|p点坐标:(0, 0),是否在图形内部:True
p点坐标:(0, 1),是否在图形内部:True|p点坐标:(0, 2),是否在图形内部:True
p点坐标:(0, 3),是否在图形内部:True|p点坐标:(0, 4),是否在图形内部:True
p点坐标:(0, 5),是否在图形内部:False|p点坐标:(0, 6),是否在图形内部:False
p点坐标:(1, -1),是否在图形内部:False|p点坐标:(1, 0),是否在图形内部:True
p点坐标:(1, 1),是否在图形内部:True|p点坐标:(1, 2),是否在图形内部:True
p点坐标:(1, 3),是否在图形内部:True|p点坐标:(1, 4),是否在图形内部:True
p点坐标:(1, 5),是否在图形内部:False|p点坐标:(1, 6),是否在图形内部:False
p点坐标:(2, -1),是否在图形内部:True|p点坐标:(2, 0),是否在图形内部:True
p点坐标:(2, 1),是否在图形内部:True|p点坐标:(2, 2),是否在图形内部:True
p点坐标:(2, 3),是否在图形内部:True|p点坐标:(2, 4),是否在图形内部:True
p点坐标:(2, 5),是否在图形内部:False|p点坐标:(2, 6),是否在图形内部:False
p点坐标:(3, -1),是否在图形内部:False|p点坐标:(3, 0),是否在图形内部:True
p点坐标:(3, 1),是否在图形内部:True|p点坐标:(3, 2),是否在图形内部:True
p点坐标:(3, 3),是否在图形内部:True|p点坐标:(3, 4),是否在图形内部:True
p点坐标:(3, 5),是否在图形内部:False|p点坐标:(3, 6),是否在图形内部:False
p点坐标:(4, -1),是否在图形内部:False|p点坐标:(4, 0),是否在图形内部:True
p点坐标:(4, 1),是否在图形内部:True|p点坐标:(4, 2),是否在图形内部:True
p点坐标:(4, 3),是否在图形内部:False|p点坐标:(4, 4),是否在图形内部:False
p点坐标:(4, 5),是否在图形内部:False|p点坐标:(4, 6),是否在图形内部:False
p点坐标:(5, -1),是否在图形内部:False|p点坐标:(5, 0),是否在图形内部:True
p点坐标:(5, 1),是否在图形内部:True|p点坐标:(5, 2),是否在图形内部:False
p点坐标:(5, 3),是否在图形内部:False|p点坐标:(5, 4),是否在图形内部:False
p点坐标:(5, 5),是否在图形内部:False|p点坐标:(5, 6),是否在图形内部:False
相关文章:

【算法学习】射线法判断点在多边形内外(C#)以及确定内外两点连线与边界的交点
1.前言: 在GIS开发中,经常会遇到确定一个坐标点是否在一块区域的内部这一问题。 如果这个问题不是一个单纯的数学问题,例如:在判断DEM、二维图像像素点、3D点云点等含有自身特征信息的这些点是否在一个区域范围内部的时候&#x…...

SQL语句(DML)
DML英文全称是Data Manipulation Language(数据操作语言),用来对数据库中表的数据记录进行增删改等操作 DML-添加数据 insert into employee(id, workno, name, gender, age, idcard) values (1,1,Itcast,男,10,123456789012345678);select *…...
uniapp小程序打开地图导航
uniapp uni.getLocation({type: gcj02, //返回可以用于uni.openLocation的经纬度success: function (res) {const latitude res.latitude;const longitude res.longitude;uni.openLocation({latitude: latitude,longitude: longitude,success: function () {console.log(suc…...

webstorm格式化或保存时 vue3引入的组件被删除了
解决办法 保存时设置 格式化设置...
Java时间转换
一、线程不安全 Date date new Date(); SimpleDateFormat dateFormat new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String prefix dateFormat.format(date);二、线程安全,建议使用 String t1 LocalDateTime.now().format(DateTimeFormatter.ofPattern("y…...
Spring Boot与WebFlux的实战案例
Spring Boot与WebFlux的实战案例 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天,我们将探讨如何利用Spring Boot和WebFlux构建响应式应用的实战…...

vue3引入本地静态资源图片
一、单张图片引入 import imgXX from /assets/images/xx.png二、多张图片引入 说明:import.meta.url 是一个 ESM 的原生功能,会暴露当前模块的 URL。将它与原生的 URL 构造器 组合使用 注意:填写自己项目图片存放的路径 /** vite的特殊性…...

git 禁止dev合并到任何其他分支
创建 pre-merge-commit 钩子 导航到 Git 仓库的钩子目录: cd /path/to/your/repo/.git/hooks创建或编辑 pre-merge-commit 钩子: 也可以通过指令创建 nano pre-merge-commit在钩子文件中添加以下代码: #!/bin/sh# 获取当前分支名称 curr…...

第二节:如何使用thymeleaf渲染html(自学Spring boot 3.x的第一天)
大家好,我是网创有方,今天来学习如何使用thymeleaf渲染html。该模板运用不广泛,所以本节内容了解既可。 第一步:创建html文件。 在模板templates目录下创建一个html文件。 编写代码如下: <!DOCTYPE html> <…...
计算机相关术语科普之什么叫网关(Gateway)
网关(Gateway)是一个在计算机网络中起到关键作用的设备或系统,它扮演着网络间连接器或协议转换器的角色。 一、定义与功能 1)定义: 网关是在不同网络之间实现互连的复杂设备,仅用于两个高层协议不同的网…...

B站网页部分API
https://www.bilibili.com/ 数据结构 mid: 用户id name: 用户名 face: 用户头像url noface.jpg为默认头像 sign: 签名level: b站等级 coins: b站硬币粉丝 https://api.bilibili.com/x/relation/fans?vmid{mid}&pn{pn}&ps{limit}&orderdesc&…...
使用Spring Boot和Spring Security保护你的应用
使用Spring Boot和Spring Security保护你的应用 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将深入探讨如何利用Spring Boot和Spring Security来保护…...

CVE-2019-12272 Openwrt可视页面LuCi命令注入漏洞复现(完结)
声明 本文所使用的一些源代码等内容已经上传至github,具体地址如下 Vulnerability_POC-EXP/OpenWrt/CVE-2019-12272 at main a2148001284/Vulnerability_POC-EXP GitHub 漏洞简介 参考内容: CVE-2019-12272 OpenWrt图形化管理界面LuCI命令注入分析 |…...
【多线程开发 4】从源码学习LockSupport
从源码学习LockSupport 2024年6月30日 大家好啊,好久没写博客了,今天打算写一下,讲一下JUC里面LockSupport这个类。 这个是一个工具类,实际上也是为了线程通信开发的。它的源码比较短,也只引用了Unsafe一个类。所以…...
gameui C++的代码
gameui C的代码 #include <graphics.h> #include "gameboard.h" const int WIDTH 560; const int HEIGHT 780; const int GRID_SIZE 120; class GameUi { private: public:GameUi(GameBoard& gb) {// 初始化图形窗口initgraph(WIDTH, HEIGHT);// 设置…...
1.什么是js?特点是什么?组成部分?
Js是一种直译式脚本语言,一种动态类型,弱类型,基于原型的高级语言。 直译式:js程序运行过程中直接编译成机器语言。 脚本语言:在程序运行过程中逐行进行解释说明,不需要预编译。 动态类型:js…...

爬虫是什么?
目录 1.什么是互联网爬虫? 2.爬虫核心? 3.爬虫的用途? 4.爬虫分类? 5.反爬手段? 1.什么是互联网爬虫? 如果我们把互联网比作一张大的蜘蛛网,那一台计算机上的数据便是蜘蛛网上的一个猎物,而爬虫程序…...
深入理解Presto分页查询:方法与最佳实践
目录 引言为什么需要分页查询Presto简介分页查询的基本概念Presto分页查询的实现方法 使用LIMIT和OFFSET使用游标分页结合外部工具和框架 分页查询的性能优化 索引优化查询计划优化数据分区 实际案例分析最佳实践与常见问题 大数据集分页复杂查询分页实时性要求高的场景 总结 …...
如何使用Go语言中的并发函数实现网络爬虫的分布式部署?
如何使用go语言中的并发函数实现网络爬虫的分布式部署? 在当今的互联网时代,大量的信息蕴藏在各个网站中,爬虫成为了一种重要的工具。而对于大规模的数据爬取任务,采用分布式部署能够更有效地提升爬取速度和效率。Go语言的并发机…...

STM32第九课:DHT11温湿度传感器
文章目录 需求一、DHT11温湿度传感器二、模块配置流程1.配置时钟和IO2.读取数据3.数据处理 三、导入语音模块四、关键代码总结 需求 1.完成DHT11温湿度检测模块的配置。 2.处理DHT11获取的数据,在串口打印处理后的实时数据。 2.通过Su-03t语音识别模块实现实时温湿…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...

9-Oracle 23 ai Vector Search 特性 知识准备
很多小伙伴是不是参加了 免费认证课程(限时至2025/5/15) Oracle AI Vector Search 1Z0-184-25考试,都顺利拿到certified了没。 各行各业的AI 大模型的到来,传统的数据库中的SQL还能不能打,结构化和非结构的话数据如何和…...