前言
在GIS(地理信息管理系统)中,判断一个坐标是否在多边形内部是个经常要遇到的问题。乍听起来还挺复杂。根据W. Randolph Franklin 提出的PNPoly算法,只需区区几行代码就解决了这个问题。PNPoly算法用来判断一个坐标点是否在不规则多边形内部。
算法详解
首先我们要知道如何判断一个点是否在多边形内部。
从该点任意引一条射线,若点在多边形内,则与多边形边的点应该为奇数个(因为不管有几个交点,最后一个交点必定是穿出多边形)。而相反,若为偶数个则点在多边形外。
PNPlot算法是从该点水平向右引一条射线,根据上面的基本法则进行判断的。下面是换算式:
代码
该代码中涉及到Java消息处理机制,以及内部类的一些知识。
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
public class Main {
private static int n = 0; // 多边形顶点个数
private static int[] x = new int[20]; // 多边形顶点坐标
private static int[] y = new int[20];
private static int testx = -1, testy = -1; // 测试点坐标
private static boolean flag = false; // 是否已经构成多边形
public static boolean pnplot(int tx, int ty) { // PNplot算法用于判断点是否在多边形内
boolean result = false;
for (int i = 1, j = n; i <= n; j = i++) {
if (((y[i] > ty) != (y[j] > ty)) && (tx < (x[j] - x[i]) * (ty - y[i]) * 1.0 / (y[j] - y[i]) + x[i])) {
result = !result;
}
}
return result;
}
public static void main(String[] args) {
MyFrame mf = new MyFrame();
MyPanel mp = new MyPanel();
mf.setContentPane(mp); // 设置窗口的内容面板
mf.setVisible(true);
MouseMotionAdapter mma = new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
x[n + 1] = e.getX();
y[n + 1] = e.getY();
mp.repaint();
}
};
mp.addMouseListener(new MouseAdapter() { // 添加鼠标监听器
@Override
public void mouseClicked(MouseEvent e) {
if (!flag) {
n++;
x[n] = e.getX();
y[n] = e.getY();
x[n + 1] = e.getX();
y[n + 1] = e.getY();
/*当前点与第一个点的距离在8以内,则形成首尾相连的多边形*/
if ((x[n] - x[1]) * (x[n] - x[1]) + (y[n] - y[1]) * (y[n] - y[1]) <= 64 && n > 1) {
x[n] = x[1];
y[n] = y[1];
flag = true;
}
mp.repaint(); // 重绘
if (flag) // 多边形已经成功绘制
mp.removeMouseMotionListener(mma); // 移除MouseMotionListener
}
/*若已经绘制好多边形,则之后的鼠标打点都转为测试点*/
else {
testx = e.getX();
testy = e.getY();
mp.repaint();
}
}
});
mp.addMouseMotionListener(mma); // 添加鼠标移动监听器
}
@SuppressWarnings("serial")
static class MyPanel extends JPanel {
public void paint(Graphics g) {
g.clearRect(0, 0, this.getWidth(), this.getHeight()); //清除
for (int i = 1; i <= n; i++) {
g.drawLine(x[i], y[i], x[i + 1], y[i + 1]);
}
if (testx != -1 && testy != -1) {
Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(new BasicStroke(3.0f)); // 设置画笔粗细
/*根据点与多边形的位置关系设置画笔颜色*/
if (pnplot(testx, testy)) {
g2d.setColor(Color.blue);
} else
g2d.setColor(Color.red);
g2d.drawLine(testx, testy, testx, testy); //绘制点
}
}
}
@SuppressWarnings("serial")
static class MyFrame extends JFrame {
MyFrame() {
setSize(500, 500); // 设置框架大小
}
}
}