package ink.wgink.util.point;

import java.math.BigDecimal;
import java.util.List;

/**
 * When you feel like quitting. Think about why you started
 * 当你想要放弃的时候，想想当初你为何开始
 *
 * @ClassName: PointUtil
 * @Description: 点工具类
 * @Author: WangGeng
 * @Date: 2019-08-09 22:43
 * @Version: 1.0
 **/
public class PointUtil {

    private static final double EARTH_RADIUS = 637_1393.00D;
    private static final double RADIAN = Math.PI / 180.00D;
    private static final double HALF = 0.5D;

    /**
     * 点在多边形内
     *
     * @param point
     * @param points
     * @return
     */
    public static boolean isPointInPoly(Point point, List<Point> points) {
        int N = points.size();
        //如果点位于多边形的顶点或边上，也算做点在多边形内，直接返回true
        boolean boundOrVertex = true;
        //cross points count of x
        int intersectCount = 0;
        //浮点类型计算时候与0比较时候的容差
        double precision = 2e-10;
        //neighbour bound vertices
        Point p1, p2;
        //当前点
        Point p = point;
        //left vertex
        p1 = points.get(0);
        //check all rays
        for (int i = 1; i <= N; ++i) {
            if (p.equals(p1)) {
                //p is an vertex
                return boundOrVertex;
            }
            //right vertex
            p2 = points.get(i % N);
            //ray is outside of our interests
            if (p.getX() < Math.min(p1.getX(), p2.getX()) || p.getX() > Math.max(p1.getX(), p2.getX())) {
                p1 = p2;
                continue;//next ray left point
            }
            //ray is crossing over by the algorithm (common part of)
            if (p.getX() > Math.min(p1.getX(), p2.getX()) && p.getX() < Math.max(p1.getX(), p2.getX())) {
                //x is before of ray
                if (p.getY() <= Math.max(p1.getY(), p2.getY())) {
                    //overlies on a horizontal ray
                    if (p1.getX() == p2.getX() && p.getY() >= Math.min(p1.getY(), p2.getY())) {
                        return boundOrVertex;
                    }
                    //ray is vertical
                    if (p1.getY() == p2.getY()) {
                        //overlies on a vertical ray
                        if (p1.getY() == p.getY()) {
                            return boundOrVertex;
                        } else {
                            //before ray
                            ++intersectCount;
                        }
                    } else {
                        //cross point on the left side
                        //cross point of y
                        double xinters = (p.getX() - p1.getX()) * (p2.getY() - p1.getY()) / (p2.getX() - p1.getX()) + p1.getY();
                        //overlies on a ray
                        if (Math.abs(p.getY() - xinters) < precision) {
                            return boundOrVertex;
                        }
                        //before ray
                        if (p.getY() < xinters) {
                            ++intersectCount;
                        }
                    }
                }
            } else {
                //special case when ray is crossing through the vertex
                //p crossing over p2
                if (p.getX() == p2.getX() && p.getY() <= p2.getY()) {
                    //next vertex
                    Point p3 = points.get((i + 1) % N);
                    //p.x lies between p1.x & p3.x
                    if (p.getX() >= Math.min(p1.getX(), p3.getX()) && p.getX() <= Math.max(p1.getX(), p3.getX())) {
                        ++intersectCount;
                    } else {
                        intersectCount += 2;
                    }
                }
            }
            //next ray left point
            p1 = p2;
        }
        if (intersectCount % 2 == 0) {
            //偶数在多边形外
            return false;
        } else {
            //奇数在多边形内
            return true;
        }
    }

    /**
     * 判断是否在圆形内
     * <p>
     * 判断点与圆心之间的距离和圆半径的关系
     *
     * @param p
     * @param c
     * @return
     */
    public static int distencePC(Point p, Circle c) {
        int s;
        double d2 = Math.hypot((p.getX() - c.getPoint().getX()), (p.getY() - c.getPoint().getY()));
        double r = c.getR();
        if (d2 > r) {
            // 圆外
            s = -1;
        } else if (d2 < r) {
            // 圆内
            s = 1;
        } else {
            // 圆上
            s = 0;
        }
        return s;
    }

    /**
     * 计算距离，Point的x为纬度，y为经度
     *
     * @param point1
     * @param point2
     * @return
     */
    public static double getDistance(Point point1, Point point2) {
        double lat1 = point1.getX();
        double lon1 = point1.getY();
        double lat2 = point2.getX();
        double lon2 = point2.getY();
        double x, y, a, b, distance;

        lat1 *= RADIAN;
        lat2 *= RADIAN;
        x = lat1 - lat2;
        y = lon1 - lon2;
        y *= RADIAN;
        a = Math.sin(x * HALF);
        b = Math.sin(y * HALF);
        distance = EARTH_RADIUS * Math.asin(Math.sqrt(a * a + Math.cos(lat1) * Math.cos(lat2) * b * b)) / HALF;
        return new BigDecimal(distance).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 计算面积
     *
     * @param points
     * @return
     */
    public static double getSqua(List<Point> points) {
        double area = 0;
        int size = points.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 = points.get(size - 1).getX() * Math.PI / 180;
                    LowY = points.get(size - 1).getY() * Math.PI / 180;
                    MiddleX = points.get(0).getX() * Math.PI / 180;
                    MiddleY = points.get(0).getY() * Math.PI / 180;
                    HighX = points.get(1).getX() * Math.PI / 180;
                    HighY = points.get(1).getY() * Math.PI / 180;
                } else if (i == size - 1) {
                    LowX = points.get(size - 2).getX() * Math.PI / 180;
                    LowY = points.get(size - 2).getY() * Math.PI / 180;
                    MiddleX = points.get(size - 1).getX() * Math.PI / 180;
                    MiddleY = points.get(size - 1).getY() * Math.PI / 180;
                    HighX = points.get(0).getX() * Math.PI / 180;
                    HighY = points.get(0).getY() * Math.PI / 180;
                } else {
                    LowX = points.get(i - 1).getX() * Math.PI / 180;
                    LowY = points.get(i - 1).getY() * Math.PI / 180;
                    MiddleX = points.get(i).getX() * Math.PI / 180;
                    MiddleY = points.get(i).getY() * Math.PI / 180;
                    HighX = points.get(i + 1).getX() * Math.PI / 180;
                    HighY = points.get(i + 1).getY() * 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);
    }

}
