前言
在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); // 設置框架大小
}
}
}