java,简单可视,计算两个经纬高坐标的连线是否穿过地球

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]
            };
        }
    }
}

没穿过地球则可视

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值