import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.text.DecimalFormat;
public class EarthPenetrationChecker extends JFrame{
private static final double EARTH_RADIUS_KM = 6371.0; // 地球平均半径(千米)
private static final DecimalFormat df = new DecimalFormat("0.00");
private JTextField lat1Field, lon1Field, alt1Field;
private JTextField lat2Field, lon2Field, alt2Field;
private JTextArea resultArea;
private EarthPanel earthPanel;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new EarthPenetrationChecker());
}
public EarthPenetrationChecker() {
setTitle("地球穿透检测器");
setSize(1000, 700);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
// 创建输入面板
JPanel inputPanel = new JPanel(new GridLayout(3, 4, 5, 5));
inputPanel.setBorder(BorderFactory.createTitledBorder("坐标输入"));
inputPanel.add(new JLabel("点1 纬度:"));
lat1Field = new JTextField("40.7128");
inputPanel.add(lat1Field);
inputPanel.add(new JLabel("点1 经度:"));
lon1Field = new JTextField("-74.0060");
inputPanel.add(lon1Field);
inputPanel.add(new JLabel("点1 高程(km):"));
alt1Field = new JTextField("0");
inputPanel.add(alt1Field);
inputPanel.add(new JLabel("点2 纬度:"));
lat2Field = new JTextField("39.9042");
inputPanel.add(lat2Field);
inputPanel.add(new JLabel("点2 经度:"));
lon2Field = new JTextField("116.4074");
inputPanel.add(lon2Field);
inputPanel.add(new JLabel("点2 高程(km):"));
alt2Field = new JTextField("0");
inputPanel.add(alt2Field);
// 创建按钮
JButton checkButton = new JButton("检测穿透");
JButton exampleButton = new JButton("示例:地表两点");
JButton satelliteButton = new JButton("示例:卫星通信");
JButton antipodeButton = new JButton("示例:对跖点");
// 创建结果区域
resultArea = new JTextArea();
resultArea.setEditable(false);
resultArea.setFont(new Font("等线", Font.PLAIN, 16));
resultArea.setBorder(BorderFactory.createTitledBorder("检测结果"));
// 创建地球可视化面板
earthPanel = new EarthPanel();
// 添加组件到主窗口
JPanel topPanel = new JPanel(new BorderLayout());
topPanel.add(inputPanel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
buttonPanel.add(checkButton);
buttonPanel.add(exampleButton);
buttonPanel.add(satelliteButton);
buttonPanel.add(antipodeButton);
topPanel.add(buttonPanel, BorderLayout.SOUTH);
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
new JScrollPane(resultArea), earthPanel);
splitPane.setDividerLocation(300);
add(topPanel, BorderLayout.NORTH);
add(splitPane, BorderLayout.CENTER);
// 按钮事件处理
checkButton.addActionListener(e -> checkPenetration());
exampleButton.addActionListener(e -> {
lat1Field.setText("40.7128"); // 纽约
lon1Field.setText("-74.0060");
alt1Field.setText("0");
lat2Field.setText("39.9042"); // 北京
lon2Field.setText("116.4074");
alt2Field.setText("0");
checkPenetration();
});
satelliteButton.addActionListener(e -> {
lat1Field.setText("40.7128"); // 纽约
lon1Field.setText("-74.0060");
alt1Field.setText("0");
lat2Field.setText("39.9042"); // 北京
lon2Field.setText("116.4074");
alt2Field.setText("20000"); // 卫星高度
checkPenetration();
});
antipodeButton.addActionListener(e -> {
lat1Field.setText("40.7128"); // 纽约
lon1Field.setText("-74.0060");
alt1Field.setText("0");
lat2Field.setText("-40.7128"); // 纽约的对跖点
lon2Field.setText("105.9940");
alt2Field.setText("0");
checkPenetration();
});
// 初始检测
checkPenetration();
setVisible(true);
}
private void checkPenetration() {
try {
// 解析输入
double lat1 = Double.parseDouble(lat1Field.getText());
double lon1 = Double.parseDouble(lon1Field.getText());
double alt1 = Double.parseDouble(alt1Field.getText());
double lat2 = Double.parseDouble(lat2Field.getText());
double lon2 = Double.parseDouble(lon2Field.getText());
double alt2 = Double.parseDouble(alt2Field.getText());
// 转换为三维坐标
double[] point1 = convertToCartesian(lat1, lon1, alt1);
double[] point2 = convertToCartesian(lat2, lon2, alt2);
// 计算穿透情况
boolean penetrates = doesLinePenetrateEarth(point1, point2);
// 计算两点距离
double surfaceDistance = calculateSurfaceDistance(lat1, lon1, lat2, lon2);
double straightDistance = calculateStraightDistance(point1, point2);
// 计算最小地心距离
double minDistance = calculateMinDistanceToCenter(point1, point2);
// 显示结果
StringBuilder result = new StringBuilder();
result.append("点1坐标: ").append(lat1).append("°, ").append(lon1).append("°, ")
.append(alt1).append(" km\n");
result.append("点2坐标: ").append(lat2).append("°, ").append(lon2).append("°, ")
.append(alt2).append(" km\n\n");
result.append("地表距离: ").append(df.format(surfaceDistance)).append(" km\n");
result.append("直线距离: ").append(df.format(straightDistance)).append(" km\n");
result.append("连线到地心最小距离: ").append(df.format(minDistance)).append(" km\n");
result.append("地球半径: ").append(EARTH_RADIUS_KM).append(" km\n\n");
if (minDistance < EARTH_RADIUS_KM) {
result.append("检测结果: 连线穿透地球!\n");
result.append("原因: 连线与地球中心的最小距离 (").append(df.format(minDistance))
.append(" km) 小于地球半径 (").append(EARTH_RADIUS_KM).append(" km)");
} else {
result.append("检测结果: 连线未穿透地球\n");
result.append("原因: 连线与地球中心的最小距离 (").append(df.format(minDistance))
.append(" km) 大于等于地球半径 (").append(EARTH_RADIUS_KM).append(" km)");
}
resultArea.setText(result.toString());
earthPanel.setPoints(point1, point2, penetrates);
earthPanel.repaint();
} catch (NumberFormatException ex) {
resultArea.setText("错误: 请输入有效的数字坐标");
}
}
/**
* 将经纬高转换为三维笛卡尔坐标
*
* @param lat 纬度
* @param lon 经度
* @param alt 高程(千米)
* @return [x, y, z] 三维坐标
*/
private double[] convertToCartesian(double lat, double lon, double alt) {
double radLat = Math.toRadians(lat);
double radLon = Math.toRadians(lon);
double r = EARTH_RADIUS_KM + alt;
double x = r * Math.cos(radLat) * Math.cos(radLon);
double y = r * Math.cos(radLat) * Math.sin(radLon);
double z = r * Math.sin(radLat);
return new double[]{x, y, z};
}
/**
* 计算地球表面最短距离(Haversine公式)
*/
private double calculateSurfaceDistance(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 deltaLat = radLat2 - radLat1;
double deltaLon = radLon2 - radLon1;
double a = Math.pow(Math.sin(deltaLat / 2), 2)
+ Math.cos(radLat1) * Math.cos(radLat2)
* Math.pow(Math.sin(deltaLon / 2), 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS_KM * c;
}
/**
* 计算两点间直线距离
*/
private double calculateStraightDistance(double[] p1, double[] p2) {
double dx = p2[0] - p1[0];
double dy = p2[1] - p1[1];
double dz = p2[2] - p1[2];
return Math.sqrt(dx*dx + dy*dy + dz*dz);
}
/**
* 计算线段到地心的最小距离
*/
private double calculateMinDistanceToCenter(double[] p1, double[] p2) {
// 线段方向向量
double[] dir = {p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]};
// 点p1到地心的向量
double[] p1ToOrigin = {-p1[0], -p1[1], -p1[2]};
// 计算线段长度平方
double dirLengthSq = dir[0]*dir[0] + dir[1]*dir[1] + dir[2]*dir[2];
// 如果线段长度为零(两点重合)
if (dirLengthSq < 1e-10) {
return Math.sqrt(p1[0]*p1[0] + p1[1]*p1[1] + p1[2]*p1[2]);
}
// 计算投影参数t
double t = (p1ToOrigin[0]*dir[0] + p1ToOrigin[1]*dir[1] + p1ToOrigin[2]*dir[2]) / dirLengthSq;
// 限制t在[0,1]范围内
t = Math.max(0, Math.min(1, t));
// 计算最近点坐标
double[] closestPoint = {
p1[0] + t * dir[0],
p1[1] + t * dir[1],
p1[2] + t * dir[2]
};
// 返回最近点到地心的距离
return Math.sqrt(
closestPoint[0]*closestPoint[0] +
closestPoint[1]*closestPoint[1] +
closestPoint[2]*closestPoint[2]
);
}
/**
* 判断两点连线是否穿透地球
*/
private boolean doesLinePenetrateEarth(double[] p1, double[] p2) {
double minDistance = calculateMinDistanceToCenter(p1, p2);
return minDistance < EARTH_RADIUS_KM;
}
// 地球可视化面板
class EarthPanel extends JPanel {
private double[] point1;
private double[] point2;
private boolean penetrates;
public void setPoints(double[] point1, double[] point2, boolean penetrates) {
this.point1 = point1;
this.point2 = point2;
this.penetrates = penetrates;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int width = getWidth();
int height = getHeight();
int centerX = width / 2;
int centerY = height / 2;
int earthRadius = Math.min(width, height) * 2 / 5;
// 绘制地球
GradientPaint gp = new GradientPaint(
centerX - earthRadius, centerY - earthRadius, new Color(30, 80, 180),
centerX + earthRadius, centerY + earthRadius, new Color(70, 130, 220)
);
g2d.setPaint(gp);
g2d.fill(new Ellipse2D.Double(centerX - earthRadius, centerY - earthRadius,
2 * earthRadius, 2 * earthRadius));
// 绘制地球轮廓
g2d.setColor(new Color(20, 50, 120));
g2d.setStroke(new BasicStroke(2));
g2d.drawOval(centerX - earthRadius, centerY - earthRadius,
2 * earthRadius, 2 * earthRadius);
// 绘制经纬度网格
g2d.setColor(new Color(100, 180, 255, 150));
g2d.setStroke(new BasicStroke(1));
g2d.drawLine(centerX - earthRadius, centerY, centerX + earthRadius, centerY);
g2d.drawLine(centerX, centerY - earthRadius, centerX, centerY + earthRadius);
g2d.drawOval(centerX - earthRadius/2, centerY - earthRadius, earthRadius, 2*earthRadius);
g2d.drawOval(centerX - earthRadius, centerY - earthRadius/2, 2*earthRadius, earthRadius);
if (point1 != null && point2 != null) {
// 转换坐标到屏幕坐标
Point p1Screen = convertToScreen(point1, centerX, centerY, earthRadius);
Point p2Screen = convertToScreen(point2, centerX, centerY, earthRadius);
// 绘制穿透地球的直线
g2d.setColor(penetrates ? new Color(255, 50, 50, 200) : new Color(50, 200, 50, 200));
g2d.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.drawLine(p1Screen.x, p1Screen.y, p2Screen.x, p2Screen.y);
// 绘制点1
g2d.setColor(new Color(255, 215, 0));
g2d.fillOval(p1Screen.x - 7, p1Screen.y - 7, 14, 14);
g2d.setColor(Color.BLACK);
g2d.drawOval(p1Screen.x - 7, p1Screen.y - 7, 14, 14);
// 绘制点2
g2d.setColor(new Color(0, 200, 255));
g2d.fillOval(p2Screen.x - 7, p2Screen.y - 7, 14, 14);
g2d.setColor(Color.BLACK);
g2d.drawOval(p2Screen.x - 7, p2Screen.y - 7, 14, 14);
// 绘制地心
g2d.setColor(Color.RED);
g2d.fillOval(centerX - 4, centerY - 4, 8, 8);
// 绘制连线到地心的最近点
double minDist = calculateMinDistanceToCenter(point1, point2);
double[] closestPoint = findClosestPointToOrigin(point1, point2);
Point closestScreen = convertToScreen(closestPoint, centerX, centerY, earthRadius);
g2d.setColor(new Color(180, 50, 230));
g2d.fillOval(closestScreen.x - 5, closestScreen.y - 5, 10, 10);
g2d.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{5}, 0));
g2d.drawLine(centerX, centerY, closestScreen.x, closestScreen.y);
// 绘制标题
g2d.setColor(Color.BLACK);
g2d.setFont(new Font("微软雅黑", Font.BOLD, 18));
g2d.drawString("地球穿透检测可视化", centerX - 100, 30);
String status = penetrates ? "连线穿透地球" : "连线未穿透地球";
g2d.setColor(penetrates ? Color.RED : new Color(0, 150, 0));
g2d.drawString("状态: " + status, centerX - 80, 60);
g2d.setColor(Color.BLACK);
g2d.setFont(new Font("微软雅黑", Font.PLAIN, 12));
g2d.drawString("金色点: 点1坐标", 20, height - 60);
g2d.drawString("蓝色点: 点2坐标", 20, height - 40);
g2d.drawString("紫色点: 到地心最近点", 20, height - 20);
}
}
/**
* 将三维坐标转换为屏幕坐标
*/
private Point convertToScreen(double[] point, int centerX, int centerY, int radius) {
// 简单正交投影(忽略y轴)
double scale = radius / EARTH_RADIUS_KM;
int x = centerX + (int)(point[0] * scale);
int y = centerY - (int)(point[2] * scale); // 注意:屏幕Y轴向下,所以取负
return new Point(x, y);
}
/**
* 计算线段上距离地心最近的点
*/
private double[] findClosestPointToOrigin(double[] p1, double[] p2) {
double[] dir = {p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]};
double[] p1ToOrigin = {-p1[0], -p1[1], -p1[2]};
double dirLengthSq = dir[0]*dir[0] + dir[1]*dir[1] + dir[2]*dir[2];
if (dirLengthSq < 1e-10) {
return p1;
}
double t = (p1ToOrigin[0]*dir[0] + p1ToOrigin[1]*dir[1] + p1ToOrigin[2]*dir[2]) / dirLengthSq;
t = Math.max(0, Math.min(1, t));
return new double[]{
p1[0] + t * dir[0],
p1[1] + t * dir[1],
p1[2] + t * dir[2]
};
}
}
}
没穿过地球则可视