在处理红外测温图片时,经常需要找出一个多边形区域的最大最小温度,测温图片里,厂家有sdk根据坐标点获取温度值的算法,此时需要有算法获取多边形内的所有坐标点。一种简单常用的算法是射线穿透法,就是往任意方向画射线,计算射线与多边形所有边的交点数量,如果数量为偶数,则该点在多边形外,否则在多边形内部。射线的方向主要用x轴正方向,或者y轴正方向。需要了解直线方程(点斜式)。需要排除射线过多边形顶点的情况,以及射线与边平行重合的情况:
推荐方法:
/**
* 判断点是否在多边形内
* 射线穿透算法,往x轴正向做射线,
* 考虑到过顶点和边平行x轴的情况,
*
* @param pts 多边形的所有顶点坐标
* @param targetPoint 要判断的点坐标
* @return
*/
public static boolean isInsidePolygon(List<Point2D.Double> pts, Point2D.Double targetPoint)
{
List<Point2D.Double> ptList = pts;
int numVertices = ptList.size();
if (numVertices < 3)
{
return false;
}
boolean inside = false;
for (int index = 0; index < numVertices; index++) {
Point2D.Double p1 = ptList.get(index);
int p2Index = (index + 1) % numVertices;
Point2D.Double p2 = ptList.get(p2Index);
if ((targetPoint.y == p1.y && targetPoint.x < p1.x) || (targetPoint.y == p2.y && targetPoint.x < p2.x))
{
int prevIndex = (index - 1 + numVertices) % numVertices;
int nextIndex = (index + 1) % numVertices;
Point2D.Double prevPoint = ptList.get(prevIndex);
Point2D.Double nextPoint = ptList.get(nextIndex);
if ((targetPoint.y < prevPoint.y && targetPoint.y >= nextPoint.y) ||
(targetPoint.y >= prevPoint.y && targetPoint.y < nextPoint.y))
{
inside = !inside;
}
}
if ((targetPoint.y < p1.y != targetPoint.y < p2.y)
&& (targetPoint.y - p1.y) / (p2.y - p1.y) * (p2.x - p1.x) + p1.x
> targetPoint.x)
{
inside = !inside;
}
}
return inside;
}
完整的类:
package com.cloud.algorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 要求多边形的像素集合,可以通过遍历多边形内的所有点,然后判断哪些像素位于多边形内部。
* 一种常用的算法是基于奇偶规则,即从任意给定的点开始,沿着任意方向画一条线,
* 然后检查该线与多边形边界的交点数量。如果交点的数量是奇数,则该点在多边形内部;如果是偶数,则在多边形外部。
*/
@Slf4j
@Component
public class PixelsUtil {
/**
* 往x轴正向做射线
* 没有考虑过顶点和边平行x轴的情况,有缺陷
* @param x
* @param y
* @param polygon
* @return
*/
public static boolean isInsidePolygon(int x, int y, int[][] polygon) {
boolean result = false;
int count = 0;
int j = polygon.length - 1;
for (int i = 0; i < polygon.length; i++) {
if (((polygon[i][1] > y) != (polygon[j][1] > y)) &&
(x < (polygon[j][0] - polygon[i][0]) * (y - polygon[i][1]) / (polygon[j][1] - polygon[i][1]) + polygon[i][0])) {
count++;
}
j = i;
}
if (count % 2 == 1) {
result = true;
}
return result;
}
/**
* 判断点是否在多边形内部的函数 浮点计算,
* 往x轴正向做射线,
* 没有考虑过顶点和边平行x轴的情况,有缺陷
* @param x
* @param y
* @param xPointList
* @param yPointList
* @return
*/
public static boolean isInsidePolygon(double x, double y, List<Double> xPointList, List<Double> yPointList) {
boolean result = false;
int count = 0;
int j = yPointList.size() - 1;
for (int i = 0; i < yPointList.size(); i++) {
if (((yPointList.get(i) > y) != (yPointList.get(j) > y)) &&
(x < (xPointList.get(j) - xPointList.get(i)) * (y - yPointList.get(i)) / (yPointList.get(j) - yPointList.get(i)) + xPointList.get(i))) {
count++;
}
j = i;
}
if (count % 2 == 1) {
result = true;
}
return result;
}
/**
* 判断点是否在多边形内, 往y轴正向做射线
* @param point 测试点
* @param pts 多边形的点
* @return boolean
*/
public static boolean isInPolygon(Point2D.Double point, List<Point2D.Double> pts,double precision){
int N = pts.size();
boolean boundOrVertex = true;
int intersectCount = 0;//交叉点数量
//double precision = 2e-10; //浮点类型计算时候与0比较时候的容差
Point2D.Double p1, p2;//临近顶点
Point2D.Double p = point; //当前点
p1 = pts.get(0);
for(int i = 1; i <= N; ++i){
if(p.equals(p1)){
return boundOrVertex;
}
p2 = pts.get(i % N);
if(p.x < Math.min(p1.x, p2.x) || p.x > Math.max(p1.x, p2.x)){
p1 = p2;
continue;
}
//射线穿过算法
if(p.x > Math.min(p1.x, p2.x) && p.x < Math.max(p1.x, p2.x)){
if(p.y <= Math.max(p1.y, p2.y)){
if(p1.x == p2.x && p.y >= Math.min(p1.y, p2.y)){
return boundOrVertex;
}
if(p1.y == p2.y){
if(p1.y == p.y){
return boundOrVertex;
}else{
++intersectCount;
}
}else{
double xinters = (p.x - p1.x) * (p2.y - p1.y) / (p2.x - p1.x) + p1.y;
if(Math.abs(p.y - xinters) < precision){
return boundOrVertex;
}
if(p.y < xinters){
++intersectCount;
}
}
}
}else{
if(p.x == p2.x && p.y <= p2.y){
Point2D.Double p3 = pts.get((i+1) % N);
if(p.x >= Math.min(p1.x, p3.x) && p.x <= Math.max(p1.x, p3.x)){
++intersectCount;
}else{
intersectCount += 2;
}
}
}
p1 = p2;
}
if(intersectCount % 2 == 0){//偶数在多边形外
return false;
} else { //奇数在多边形内
return true;
}
}
/**
* 判断点是否在多边形内
* 射线穿透算法,往x轴正向做射线,
* 考虑到过顶点和边平行x轴的情况,
*
* @param pts 多边形的所有顶点坐标
* @param targetPoint 要判断的点坐标
* @return
*/
public static boolean isInsidePolygon(List<Point2D.Double> pts, Point2D.Double targetPoint)
{
List<Point2D.Double> ptList = pts;
int numVertices = ptList.size();
if (numVertices < 3)
{
return false;
}
boolean inside = false;
for (int index = 0; index < numVertices; index++) {
Point2D.Double p1 = ptList.get(index);
int p2Index = (index + 1) % numVertices;
Point2D.Double p2 = ptList.get(p2Index);
if ((targetPoint.y == p1.y && targetPoint.x < p1.x) || (targetPoint.y == p2.y && targetPoint.x < p2.x))
{
int prevIndex = (index - 1 + numVertices) % numVertices;
int nextIndex = (index + 1) % numVertices;
Point2D.Double prevPoint = ptList.get(prevIndex);
Point2D.Double nextPoint = ptList.get(nextIndex);
if ((targetPoint.y < prevPoint.y && targetPoint.y >= nextPoint.y) ||
(targetPoint.y >= prevPoint.y && targetPoint.y < nextPoint.y))
{
inside = !inside;
}
}
if ((targetPoint.y < p1.y != targetPoint.y < p2.y)
&& (targetPoint.y - p1.y) / (p2.y - p1.y) * (p2.x - p1.x) + p1.x
> targetPoint.x)
{
inside = !inside;
}
}
return inside;
}
/**
* 获取多边形内的所有坐标
*
* @param xPointList
* @param yPointList
* @param precision
* @return
*/
public Set<Point2D.Double> getPolygonPixels(List<Double> xPointList, List<Double> yPointList,double precision ) {
Set<Point2D.Double> pixels = new HashSet<>();
Double minX = Double.MAX_VALUE, maxX = Double.MIN_VALUE;
Double minY = Double.MAX_VALUE, maxY = Double.MIN_VALUE;
List<Point2D.Double> pts = new ArrayList<Point2D.Double>();
//先算出多边形的边界,x、y的最大最小值
for(int i=0;i<xPointList.size();i++){
double xv = xPointList.get(i);
double yv = yPointList.get(i);
minX = Math.min(minX, xv);
maxX = Math.max(maxX, xv);
minY = Math.min(minY, yv);
maxY = Math.max(maxY, yv);
Point2D.Double p = new Point2D.Double(xv,yv);
pts.add(p);
}
// 遍历框内的所有点
for (double x = minX+precision; x < maxX; x+=precision) {
for (double y = minY+precision; y < maxY; y+=precision) {
//if (isInsidePolygon(x, y, xPointList,yPointList)) {
Point2D.Double p = new Point2D.Double(x,y);
if(isInPolygon(p,pts,precision)) {
pixels.add(p);
}
// }
}
}
return pixels;
}
public static void main(String[] args) {
// 多边形的顶点,例如:{{x1, y1}, {x2, y2}, ..., {xn, yn}}
int[][] polygon = {{0, 0}, {2, 0}, {2, 2}, {0, 2}};
/*PixelsUtil polygonPixels = new PixelsUtil();
System.out.println("多边形内的像素集合: " + polygonPixels);
for(Point p:points){
System.out.println(p);
}*/
}
}
另外在计算网格点的时候,经常需要判断网格点是否在航线区域内,涉及圆是否与矩形有交点的计算:
package com.cloud.algorithm;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
public class CircleRectangleIntersection {
private static final double EARTH_RADIUS = 6371000; // 地球半径,单位米
/**
* 判断点是否在多边形内
*
* @param point 测试点
* @param pts 多边形的点
* @return
*/
public static boolean isInPolygon(Point2D.Double point, List<Point2D.Double> pts) {
int N = pts.size();
boolean boundOrVertex = true;
int intersectCount = 0;// 交叉点数量
double precision = 2e-10; // 浮点类型计算时候与0比较时候的容差
Point2D.Double p1, p2;// 临近顶点
Point2D.Double p = point; // 当前点
p1 = pts.get(0);
for (int i = 1; i <= N; ++i) {
if (p.equals(p1)) {
return boundOrVertex;
}
p2 = pts.get(i % N);
if (p.x < Math.min(p1.x, p2.x) || p.x > Math.max(p1.x, p2.x)) {
p1 = p2;
continue;
}
/**
* 射线穿过算法,向y轴正方向做射线
*/
if (p.x > Math.min(p1.x, p2.x) && p.x < Math.max(p1.x, p2.x)) {
if (p.y <= Math.max(p1.y, p2.y)) {
if (p1.x == p2.x && p.y >= Math.min(p1.y, p2.y)) {
//点在边线段上
return boundOrVertex;
}
if (p1.y == p2.y) {
if (p1.y == p.y) {
//点在边线段上
return boundOrVertex;
} else {
//点与边有交点
++intersectCount;
}
} else {
double xinters = (p.x - p1.x) * (p2.y - p1.y) / (p2.x - p1.x) + p1.y;
if (Math.abs(p.y - xinters) < precision) {
//点在边线段上
return boundOrVertex;
}
if (p.y < xinters) {
//点与边有交点
++intersectCount;
}
}
}
} else {
if (p.x == p2.x && p.y <= p2.y) {
Point2D.Double p3 = pts.get((i + 1) % N);
if (p.x >= Math.min(p1.x, p3.x) && p.x <= Math.max(p1.x, p3.x)) {
++intersectCount;
} else {
intersectCount += 2;
}
}
}
p1 = p2;
}
if (intersectCount % 2 == 0) {// 偶数在多边形外
return false;
} else { // 奇数在多边形内
return true;
}
}
/**
* 计算两点之间的距离(地球表面距离)
*
* @param lat1 点1 纬度
* @param lon1 点1 经度
* @param lat2 点2 纬度
* @param lon2 点2 经度
* @return 两点之间的距离(米)
*/
public static double distance(double lat1, double lon1, double lat2, double lon2) {
double radLat1 = Math.toRadians(lat1);
double radLon1 = Math.toRadians(lon1);
double radLat2 = Math.toRadians(lat2);
double radLon2 = Math.toRadians(lon2);
double a = radLat1 - radLat2;
double b = radLon1 - radLon2;
double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
+ Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
return s * EARTH_RADIUS;
}
/**
* 判断圆是否与矩形有交集
*
* @param r 圆半径(米)
* @param latCenter 圆心纬度
* @param lonCenter 圆心经度
* @param lat1 矩形最小纬度
* @param lon1 矩形最小经度
* @param lat2 矩形最大纬度
* @param lon2 矩形最大经度
* @return true 如果圆与矩形有交集,否则 false
*/
public static boolean isCircleRectangleIntersect(double r, double latCenter, double lonCenter,
double lat1, double lon1, double lat2, double lon2) {
// 矩形四个顶点
List<Point2D.Double> rectanglePoints = new ArrayList<>();
rectanglePoints.add(new Point2D.Double(lon1, lat1));
rectanglePoints.add(new Point2D.Double(lon2, lat1));
rectanglePoints.add(new Point2D.Double(lon2, lat2));
rectanglePoints.add(new Point2D.Double(lon1, lat2));
// 1. 检查圆心是否在矩形内
if (isInPolygon(new Point2D.Double(lonCenter, latCenter), rectanglePoints)) {
return true;
}
// 2. 检查矩形的四个顶点是否在圆内
for (Point2D.Double point : rectanglePoints) {
double dist = distance(latCenter, lonCenter, point.y, point.x);
if (dist <= r) {
return true;
}
}
// 3. 检查圆的边界是否与矩形有交集
// 由于地球是球体,直接判断圆的边界是否与矩形边界相交比较复杂
// 可以通过判断圆心到矩形边界的距离是否小于等于圆半径来近似判断
// 这里只检查圆心到矩形的四条边的距离,可以根据实际情况进行调整
double distToBottom = distance(latCenter, lonCenter, lat1, lonCenter);
double distToTop = distance(latCenter, lonCenter, lat2, lonCenter);
double distToLeft = distance(latCenter, lonCenter, latCenter, lon1);
double distToRight = distance(latCenter, lonCenter, latCenter, lon2);
if (distToBottom <= r || distToTop <= r || distToLeft <= r || distToRight <= r) {
return true;
}
return false;
}
public static void main(String[] args) {
double r = 1000; // 半径为1000米
double latCenter = 40.0; // 圆心纬度
double lonCenter = 116.0; // 圆心经度
double lat1 = 39.9; // 最小纬度
double lon1 = 115.9; // 最小经度
double lat2 = 40.1; // 最大纬度
double lon2 = 116.1; // 最大经度
if (isCircleRectangleIntersect(r, latCenter, lonCenter, lat1, lon1, lat2, lon2)) {
System.out.println("圆与矩形有交集");
} else {
System.out.println("圆与矩形无交集");
}
}
}