package com.keystar.plane.inspection.utils;

import com.keystar.plane.inspection.bo.Line;
import com.keystar.plane.inspection.bo.Location;
import lombok.extern.slf4j.Slf4j;

import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.util.*;

/**
 * @program:
 * @description 获取两条经纬度线段的交点坐标工具类
 * @author: Lucien
 * @create: 2020-11-27 09:21
 **/
@Slf4j
public class GisCheckUtils {
    /**
     * 地球周长
     */
    private static double L = 6381372 * Math.PI * 2;
    /**
     * 平面展开后，x轴等于周长
     */
    private static double W = L;
    /**
     * y轴约等于周长一半
     */
    private static double H = L / 2;
    /**
     * 米勒投影中的一个常数，范围大约在正负2.3之间
     */
    private static double mill = 2.3;


    /**
     * 地球半径（米）
     * 6371000.0 ~ 6371009.0
     */
    private static double earthRadiusMeters = 6371006.0;

    /**
     * 米每度
     */
    private static double metersPerDegree = 2.0 * Math.PI * earthRadiusMeters / 360.0;

    /**
     * 弧度每度
     */
    private static double radiansPerDegree = Math.PI / 180.0;



    /**
     * 将经纬度转换成X和Y轴
     * 米勒投影算法
     *
     * @param lat 纬度
     * @param lon 经度
     * @return
     */
    private static Point millierConvertion(double lat, double lon) {
        // 将经度从度数转换为弧度
        double x = lon * Math.PI / 180;
        // 将纬度从度数转换为弧度
        double y = lat * Math.PI / 180;
        // 米勒投影的转换
        y = 1.25 * Math.log(Math.tan(0.25 * Math.PI + 0.4 * y));
        // 弧度转为实际距离
        x = (W / 2) + (W / (2 * Math.PI)) * x;
        y = (H / 2) - (H / (2 * mill)) * y;
        return new Point(x, y);
    }

    /**
     * xy轴转坐标
     *
     * @param x
     * @param y
     * @return 坐标点
     */
    private static Location xyToLocation(double x, double y) {
        //实际距离 转为弧度
        x = (x - (W / 2)) / (W / (2 * Math.PI));
        y = -1 * (y - (H / 2)) / (H / (2 * mill));
        // 米勒投影的转换反转
        y = (Math.atan(Math.pow(Math.E, y / 1.25)) - 0.25 * Math.PI) / 0.4;
        //将经度从弧度转换为度数
        double lon = 180 / Math.PI * x;
        //将纬度从弧度转换为度数
        double lat = 180 / Math.PI * y;
        return new Location(lon, lat);
    }


    /**
     * 获取两条直线相交的点
     *
     * @param LineOne 线段一
     * @param LineTwo 线段二
     * @return 相交点坐标  为null 不相交
     */
    public static Location getIntersectPoint(Line LineOne, Line LineTwo) {
        //先判断有没有相交 (延长线不算) 不相交返回null 相交执行获取交点坐标 （如需获取延长线交点注释此方法）----
        if (!segIntersect(LineOne, LineTwo)) {
            return null;
        }

        //转换对象
        Point[] points = LineToPoint(LineOne, LineTwo);
        //获取两条直线相交的点
        Point Location = getIntersectPoint(points[0], points[1], points[2], points[3]);
        if (Location != null) {
            return xyToLocation(Location.x, Location.y);
        }
        return null;
    }
    /**
     * 获取两条直线list所有相交的点
     *
     * @param LineOneList 线段集合一
     * @param LineTwoList 线段集合二
     * @return 相交点坐标  为null 不相交
     */
    public static List<Location> getIntersectPointList(List<Line> LineOneList, List<Line> LineTwoList) {
        List<Location> locationList = new ArrayList<>();
        for (Line line : LineOneList) {
            for (Line line1 : LineTwoList) {
                Location location = getIntersectPoint(line,line1);
                if (Objects.nonNull(location)) {
                    locationList.add(location);
                }
            }
        }
        return locationList;
    }

    /**
     * 获取两条直线相交的点
     *
     * @param p1 线段一 开始点
     * @param p2 线段一 结束点
     * @param p3 线段二 开始点
     * @param p4 线段二 结束点
     * @return
     */
    public static Point getIntersectPoint(Point p1, Point p2, Point p3, Point p4) {

        double A1 = p1.getY() - p2.getY();
        double B1 = p2.getX() - p1.getX();
        double C1 = A1 * p1.getX() + B1 * p1.getY();

        double A2 = p3.getY() - p4.getY();
        double B2 = p4.getX() - p3.getX();
        double C2 = A2 * p3.getX() + B2 * p3.getY();

        double det_k = A1 * B2 - A2 * B1;

        if (Math.abs(det_k) < 0.00001) {
            return null;
        }

        double a = B2 / det_k;
        double b = -1 * B1 / det_k;
        double c = -1 * A2 / det_k;
        double d = A1 / det_k;

        double x = a * C1 + b * C2;
        double y = c * C1 + d * C2;

        return new Point(x, y);
    }

    /**
     * 验证两条线有没有相交
     *
     * @param LineOne 线段1
     * @param LineTwo 线段2
     * @return true 相交
     */
    public static boolean segIntersect(Line LineOne, Line LineTwo) {
        //转换对象
        Point[] points = LineToPoint(LineOne, LineTwo);
        //验证两条线有没有相交
        return segIntersect(points[0], points[1], points[2], points[3]) > 0;

    }

    /**
     * 线段转换为点对象
     *
     * @param LineOne 线段一
     * @param LineTwo 线段二
     * @return 点对象数组
     */
    private static Point[] LineToPoint(Line LineOne, Line LineTwo) {
        //线段1
        Double oneStartLat = LineOne.getStartLocation().getLat();
        Double oneStartLon = LineOne.getStartLocation().getLon();
        Double oneEndLat = LineOne.getEndLocation().getLat();
        Double oneEndLon = LineOne.getEndLocation().getLon();
        // 线段2
        Double twoStartLat = LineTwo.getStartLocation().getLat();
        Double twoStartLon = LineTwo.getStartLocation().getLon();
        Double twoEndLat = LineTwo.getEndLocation().getLat();
        Double twoEndLon = LineTwo.getEndLocation().getLon();
        Point[] points = new Point[4];
        //将经纬度转换成X和Y轴
        points[0] = millierConvertion(oneStartLat, oneStartLon);
        points[1] = millierConvertion(oneEndLat, oneEndLon);
        points[2] = millierConvertion(twoStartLat, twoStartLon);
        points[3] = millierConvertion(twoEndLat, twoEndLon);

        return points;

    }

    /**
     * 验证两条线有没有相交
     *
     * @param A 线段一 开始点
     * @param B 线段一 结束点
     * @param C 线段二 开始点
     * @param D 线段二 结束点
     * @return
     */
    public static int segIntersect(Point A, Point B, Point C, Point D) {
        Point intersection = new Point();

        if (Math.abs(B.getY() - A.getY()) + Math.abs(B.getX() - A.getX()) + Math.abs(D.getY() - C.getY())
                + Math.abs(D.getX() - C.getX()) == 0) {
            if ((C.getX() - A.getX()) + (C.getY() - A.getY()) == 0) {
                log.info("ABCD是同一个点！");
            } else {
                log.info("AB是一个点，CD是一个点，且AC不同！");
            }
            return 0;
        }

        if (Math.abs(B.getY() - A.getY()) + Math.abs(B.getX() - A.getX()) == 0) {
            if ((A.getX() - D.getX()) * (C.getY() - D.getY()) - (A.getY() - D.getY()) * (C.getX() - D.getX()) == 0) {
                log.info("A、B是一个点，且在CD线段上！");
            } else {
                log.info("A、B是一个点，且不在CD线段上！");
            }
            return 0;
        }
        if (Math.abs(D.getY() - C.getY()) + Math.abs(D.getX() - C.getX()) == 0) {
            if ((D.getX() - B.getX()) * (A.getY() - B.getY()) - (D.getY() - B.getY()) * (A.getX() - B.getX()) == 0) {
                log.info("C、D是一个点，且在AB线段上！");
            } else {
                log.info("C、D是一个点，且不在AB线段上！");
            }
            return 0;
        }

        if ((B.getY() - A.getY()) * (C.getX() - D.getX()) - (B.getX() - A.getX()) * (C.getY() - D.getY()) == 0) {
            log.info("线段平行，无交点！");
            return 0;
        }

        intersection
                .setX(((B.getX() - A.getX()) * (C.getX() - D.getX())
                        * (C.getY() - A.getY()) - C.getX()
                        * (B.getX() - A.getX()) * (C.getY() - D.getY()) + A
                        .getX() * (B.getY() - A.getY()) * (C.getX() - D.getX()))
                        / ((B.getY() - A.getY()) * (C.getX() - D.getX()) - (B
                        .getX() - A.getX()) * (C.getY() - D.getY())));
        intersection
                .setY(((B.getY() - A.getY()) * (C.getY() - D.getY())
                        * (C.getX() - A.getX()) - C.getY()
                        * (B.getY() - A.getY()) * (C.getX() - D.getX()) + A
                        .getY() * (B.getX() - A.getX()) * (C.getY() - D.getY()))
                        / ((B.getX() - A.getX()) * (C.getY() - D.getY()) - (B
                        .getY() - A.getY()) * (C.getX() - D.getX())));

        if ((intersection.getX() - A.getX()) * (intersection.getX() - B.getX()) <= 0
                && (intersection.getX() - C.getX())
                * (intersection.getX() - D.getX()) <= 0
                && (intersection.getY() - A.getY())
                * (intersection.getY() - B.getY()) <= 0
                && (intersection.getY() - C.getY())
                * (intersection.getY() - D.getY()) <= 0) {

            if ((A.getX() == C.getX() && A.getY() == C.getY()) || (A.getX() == D.getX() && A.getY() == D.getY())
                    || (B.getX() == C.getX() && B.getY() == C.getY()) || (B.getX() == D.getX() && B.getY() == D.getY())) {

//                log.info("线段相交于端点上");
                return 2;
            } else {
//                log.info("线段相交于点(" + intersection.getX() + ","
//                        + intersection.getY() + ")！");
                //相交
                return 1;
            }
        } else {
//            log.info("线段相交于虚交点(" + intersection.getX() + ","
//                    + intersection.getY() + ")！");
            //相交但不在线段上
            return -1;
        }
    }
    /**
     * 点对象
     */
    public static class Point {
        private double x;
        private double y;

        public Point() {
        }

        public Point(double x, double y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public String toString() {
            return  x  + ","+ y ;
        }

        public double getX() {
            return x;
        }

        public void setX(double x) {
            this.x = x;
        }

        public double getY() {
            return y;
        }

        public void setY(double y) {
            this.y = y;
        }

    }

    /**
     * 球面积计算公式
     *
     * @param locationList
     * @return
     */
    public static double calculatePolygonArea(List<Location> locationList) {
        double area = 0;
        int size = locationList.size();
        if (size > 2) {
            double LowX = 0.0;
            double LowY = 0.0;
            double MiddleX = 0.0;
            double MiddleY = 0.0;
            double HighX = 0.0;
            double HighY = 0.0;

            double AM = 0.0;
            double BM = 0.0;
            double CM = 0.0;

            double AL = 0.0;
            double BL = 0.0;
            double CL = 0.0;

            double AH = 0.0;
            double BH = 0.0;
            double CH = 0.0;

            double CoefficientL = 0.0;
            double CoefficientH = 0.0;

            double ALtangent = 0.0;
            double BLtangent = 0.0;
            double CLtangent = 0.0;

            double AHtangent = 0.0;
            double BHtangent = 0.0;
            double CHtangent = 0.0;

            double ANormalLine = 0.0;
            double BNormalLine = 0.0;
            double CNormalLine = 0.0;

            double OrientationValue = 0.0;

            double AngleCos = 0.0;

            double Sum1 = 0.0;
            double Sum2 = 0.0;
            double Count2 = 0;
            double Count1 = 0;

            double Sum = 0.0;
            double Radius = 6378000;

            for (int i = 0; i < size; i++) {
                if (i == 0) {
                    LowX = locationList.get(size - 1).getLon() * Math.PI / 180;
                    LowY = locationList.get(size - 1).getLat() * Math.PI / 180;
                    MiddleX = locationList.get(0).getLon() * Math.PI / 180;
                    MiddleY = locationList.get(0).getLat() * Math.PI / 180;
                    HighX = locationList.get(1).getLon() * Math.PI / 180;
                    HighY = locationList.get(1).getLat() * Math.PI / 180;
                } else if (i == size - 1) {
                    LowX = locationList.get(size - 2).getLon() * Math.PI / 180;
                    LowY = locationList.get(size - 2).getLat() * Math.PI / 180;
                    MiddleX = locationList.get(size - 1).getLon() * Math.PI / 180;
                    MiddleY = locationList.get(size - 1).getLat() * Math.PI / 180;
                    HighX = locationList.get(0).getLon() * Math.PI / 180;
                    HighY = locationList.get(0).getLat() * Math.PI / 180;
                } else {
                    LowX = locationList.get(i - 1).getLon() * Math.PI / 180;
                    LowY = locationList.get(i - 1).getLat() * Math.PI / 180;
                    MiddleX = locationList.get(i).getLon() * Math.PI / 180;
                    MiddleY = locationList.get(i).getLat() * Math.PI / 180;
                    HighX = locationList.get(i + 1).getLon() * Math.PI / 180;
                    HighY = locationList.get(i + 1).getLat() * Math.PI / 180;
                }

                AM = Math.cos(MiddleY) * Math.cos(MiddleX);
                BM = Math.cos(MiddleY) * Math.sin(MiddleX);
                CM = Math.sin(MiddleY);
                AL = Math.cos(LowY) * Math.cos(LowX);
                BL = Math.cos(LowY) * Math.sin(LowX);
                CL = Math.sin(LowY);
                AH = Math.cos(HighY) * Math.cos(HighX);
                BH = Math.cos(HighY) * Math.sin(HighX);
                CH = Math.sin(HighY);

                CoefficientL = (AM * AM + BM * BM + CM * CM) / (AM * AL + BM * BL + CM * CL);
                CoefficientH = (AM * AM + BM * BM + CM * CM) / (AM * AH + BM * BH + CM * CH);

                ALtangent = CoefficientL * AL - AM;
                BLtangent = CoefficientL * BL - BM;
                CLtangent = CoefficientL * CL - CM;
                AHtangent = CoefficientH * AH - AM;
                BHtangent = CoefficientH * BH - BM;
                CHtangent = CoefficientH * CH - CM;

                AngleCos = (AHtangent * ALtangent + BHtangent * BLtangent + CHtangent * CLtangent) / (
                        Math.sqrt(AHtangent * AHtangent + BHtangent * BHtangent + CHtangent * CHtangent)
                                * Math.sqrt(ALtangent * ALtangent + BLtangent * BLtangent
                                + CLtangent * CLtangent));

                AngleCos = Math.acos(AngleCos);

                ANormalLine = BHtangent * CLtangent - CHtangent * BLtangent;
                BNormalLine = 0 - (AHtangent * CLtangent - CHtangent * ALtangent);
                CNormalLine = AHtangent * BLtangent - BHtangent * ALtangent;

                if (AM != 0) {
                    OrientationValue = ANormalLine / AM;
                } else if (BM != 0) {
                    OrientationValue = BNormalLine / BM;
                } else {
                    OrientationValue = CNormalLine / CM;
                }

                if (OrientationValue > 0) {
                    Sum1 += AngleCos;
                    Count1++;
                } else {
                    Sum2 += AngleCos;
                    Count2++;
                    //Sum +=2*Math.PI-AngleCos;
                }
            }
            if (Sum1 > Sum2) {
                Sum = Sum1 + (2 * Math.PI * Count2 - Sum2);
            } else {
                Sum = (2 * Math.PI * Count1 - Sum1) + Sum2;
            }
            //平方米
            area = (Sum - (size - 2) * Math.PI) * Radius * Radius;
        }
        return Math.abs(area);
    }
    /**
     * 求重合面积
     *
     * @param circleList
     * @param locationList
     * @return
     */
    public static double CoincidentArea(List<Location> circleList, List<Location> locationList) {
        List<Location> areaList = new ArrayList<>();
        //先处理所有的点，防止顺序不对
        circleList = sortLocation(circleList);
        locationList = sortLocation(locationList);
        //获取圈地线段
        List<Line> circleLineList = getLines(circleList);
        //获取线段
        List<Line> lineList = getLines(locationList);

        //判断是否相交
        boolean isIntersect = isIntersect(circleLineList, lineList);
        if (isIntersect) {
            areaList = GisCheckUtils.getIntersectPointList(circleLineList, lineList);
            //判断圈地的点是否在范围内
            for (Location location : circleList) {
                boolean isPointInPolygon = isPointInPolygon(location, locationList);
                if (isPointInPolygon){
                    areaList.add(location);
                }
            }
            //判断是否在圈地的点范围内
            for (Location location : locationList) {
                boolean isPointInPolygon = isPointInPolygon(location, circleList);
                if (isPointInPolygon) {
                    areaList.add(location);
                }
            }

//            double area = GisCheckUtils.calculatePolygonArea(sortLocation(areaList));
            double area = GisCheckUtils.planarPolygonAreaMeters(sortLocation(areaList));
            log.info("重合区域面积为: " + area + "平方米");
            return area;
        }
        //如果不相交判断是否包含-由于没有相交线段只要存在点在多边形内就说明包含
        //先判断圈地是否被包含
        boolean isPointInPolygon = isPolygonInPolygon(circleList,locationList);
        if (isPointInPolygon) {
//            double area = GisCheckUtils.calculatePolygonArea(circleList);
            double area = GisCheckUtils.planarPolygonAreaMeters(circleList);
            log.info("重合区域面积为: " + area + "平方米");
            return area;
        }
        //如果不相交判断是否包含-由于没有相交线段只要存在点在多边形内就说明包含
        //再判断圈地是否包含
        isPointInPolygon = isPolygonInPolygon(locationList,circleList);
        if (isPointInPolygon) {
//            double area = GisCheckUtils.calculatePolygonArea(locationList);
            double area = GisCheckUtils.planarPolygonAreaMeters(locationList);
            log.info("重合区域面积为: " + area + "平方米");
            return area;
        }
        return  0;
    }
    /**
     * 判断第一个多边形是否存在一个点在第二个多边形内
     *
     * @param locationList1
     * @param locationList2
     * @return
     */
    public static boolean isPolygonInPolygon(List<Location> locationList1, List<Location> locationList2) {

        //判断第一个多边形是否在第二个多边形内
        for (Location location : locationList1) {
            boolean isPointInPolygon = isPointInPolygon(location, locationList2);
            if (isPointInPolygon) return true;
        }
        return false;
    }

    /**
     * 判断第一个多边形是否全部在第二个多边形内
     *
     * @param locationList1
     * @param locationList2
     * @return
     */
    public static boolean isPolygonAllInPolygon(List<Location> locationList1, List<Location> locationList2) {

        //判断第一个多边形是否在第二个多边形内
        for (Location location : locationList1) {
            boolean isPointInPolygon = isPointInPolygon(location, locationList2);
            if (!isPointInPolygon) return false;
        }
        return true;
    }

    /**
     * 判断点是否在多边形内
     *
     * @param location
     * @param locationList2
     * @return
     */
    public static boolean isPointInPolygon(Location location, List<Location> locationList2) {
        //点是否在多边形内
        GeneralPath path = new GeneralPath();
        //设定多边形起始点
        path.moveTo(locationList2.get(0).getLon(), locationList2.get(0).getLat());
        for (Location l : locationList2) {
            path.lineTo(l.getLon(), l.getLat());
        }
        //图像完成，封闭
        path.moveTo(locationList2.get(0).getLon(), locationList2.get(0).getLat());
        //多边形结束
        path.closePath();
        return path.contains(location.getLon(), location.getLat());
    }
    /**
     * 判断线段是否相交
     *
     * @param lineList
     * @param lineList2
     * @return
     */
    private static boolean isIntersect(List<Line> lineList, List<Line> lineList2) {
        for (Line line : lineList) {
            for (Line line1 : lineList2) {
                //两条线段是否相交
                boolean b = Line2D.linesIntersect(line.getStartLocation().getLon(), line.getStartLocation().getLat(), line.getEndLocation().getLon(), line.getEndLocation().getLat(),
                        line1.getStartLocation().getLon(), line1.getStartLocation().getLat(), line1.getEndLocation().getLon(), line1.getEndLocation().getLat());
                if (b) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 根据点获取线段
     *
     * @param locationList
     * @return
     */
    private static List<Line> getLines(List<Location> locationList) {
        List<Line> lineList = new ArrayList();
        for (int i = 0; i < locationList.size(); i++) {
            if (i < locationList.size() - 1) {
                Location l = locationList.get(i);
                Location l2 = locationList.get(i + 1);
                Line line = new Line(l, l2);
                lineList.add(line);
            } else {
                Location l = locationList.get(i);
                Location l2 = locationList.get(0);
                Line line = new Line(l, l2);
                lineList.add(line);
            }
        }
        return lineList;
    }

    /**
     * 将多边形的点排序
     *
     * @param locationList
     * @return
     */
    public static List<Location> sortLocation(List<Location> locationList) {
        double plusX = 0, plusY = 0;

        for (Location location : locationList) {

            plusX += location.getLon();

            plusY += location.getLat();

        }
        Location location = new Location(plusX / locationList.size(), plusY / locationList.size());

        Map<Integer,List> mapAll = new HashMap<>();
        for (int i = 0; i < locationList.size(); i++) {
            //第一个放经纬度 第二个放角度
            List objList = new ArrayList<>();
            objList.add(locationList.get(i));
            objList.add(getAngle1(location.getLat(), location.getLon(),
                    locationList.get(i).getLat(), locationList.get(i).getLon()));
            mapAll.put(i, objList);
        }
        List temp = new ArrayList<>();
        int size = mapAll.size();
        for (int i = 0; i < size - 1; i++) {
            for (int j = 0; j < size - 1 - i; j++) {
                if (Double.parseDouble(mapAll.get(j).get(1).toString()) >
                        Double.parseDouble(mapAll.get(j + 1).get(1).toString())) //交换两数位置
                {
                    temp = mapAll.get(j);
                    mapAll.put(j, mapAll.get(j + 1));
                    mapAll.put(j + 1, temp);
                }
            }
        }
        //生成新的顺时针的坐标点集合
        locationList.clear();
        for (Integer integer : mapAll.keySet()) {
            if (mapAll.get(integer).get(0) instanceof Location) {
                locationList.add((Location) mapAll.get(integer).get(0));
            }
        }
        return  locationList;
    }

    /**
     * 将多边形的点排序
     *
     * @param lat_a 纬度1
     * @param lng_a 经度1
     * @param lat_b 纬度2
     * @param lng_b 经度2
     * @return
     */
    private static double getAngle1(double lat_a, double lng_a, double lat_b, double lng_b) {

        double y = Math.sin(lng_b - lng_a) * Math.cos(lat_b);

        double x = Math.cos(lat_a) * Math.sin(lat_b) - Math.sin(lat_a) * Math.cos(lat_b) * Math.cos(lng_b - lng_a);

        double brng = Math.atan2(y, x);

        brng = Math.toDegrees(brng);

        if (brng < 0)

            brng = brng + 360;

        return brng;
    }

    /**
     * 平面多边形面积
     * @return double
     */
    public static double planarPolygonAreaMeters(List<Location> locationList) {
        // 现对坐标点进行排序
        List<Location> list = sortLocation(locationList);

        double a = 0;
        for (int i = 0; i < list.size(); i++) {
            int j = (i + 1) % list.size();

            Location point_i = list.get(i);
            double xi = point_i.getLon() * metersPerDegree * Math.cos(point_i.getLat() * radiansPerDegree);
            double yi = point_i.getLat() * metersPerDegree;

            Location point_j = list.get(j);
            double xj = point_j.getLon() * metersPerDegree * Math.cos(point_j.getLat() * radiansPerDegree);
            double yj = point_j.getLat() * metersPerDegree;

            a += xi * yj - xj * yi;
        }
        // 转化为正数
        return Math.abs(a / 2);
    }


}
