算法 - 凸包(Graham算法)

定义:在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包

前置:B点位于向量 \overrightarrow{0A} 的左边, \overrightarrow{0A} 与 \overrightarrow{OB}叉积为正

           B点位于向量 \overrightarrow{0A} 的右边,\overrightarrow{0A} 与 \overrightarrow{OB} 叉积为负

           A = \begin{pmatrix}x _{1}\\y _{1}\\ 0 \end{pmatrix}      B=\begin{pmatrix} x_{2}\\ y_{2}\\ 0 \end{pmatrix}        C = \begin{pmatrix} x_{3}\\ y_{3}\\ 0 \end{pmatrix}   \overrightarrow{AB} = \begin{pmatrix} x_{2} - x_{1}\\ y_{2} - y_{1}\\ 0 \end{pmatrix}   \overrightarrow{AC} = \begin{pmatrix} x_{3} - x_{1}\\ y_{3} - y_{1}\\ 0 \end{pmatrix}

         \overrightarrow{AB} \times \overrightarrow{AC} = \begin{pmatrix} i & j & k\\ x_{2}-x_{1}& y_{2}-y_{1} &0 \\ x_{3} - x_{1}& y_{3}-y_{1} & 0 \end{pmatrix} = \begin{pmatrix} x_{2} - x_{1} & y_{2} -y_{1}\\ x_{3}-x_{1}& y_{3}-y_{1} \end{pmatrix}k

         \left | \overrightarrow{AB} \times \overrightarrow{AC} \right | = \begin{vmatrix} x_{2}-x_{1} & y_{2}-y_{1}\\ x_{3}-x_{1} & y_{3}-y_{1} \end{vmatrix} = \begin{vmatrix} 0 & 0 & 1 \\ x_{2}-x_{1} & y_{2}-y_{1} & 1 \\ x_{3}-x_{1} & y_{3}-y_{1} & 1 \end{vmatrix}= \begin{vmatrix} x_{1} & y_{1} & 1 \\ x_{2} & y_{2} & 1 \\ x_{3}& y_{3} & 1 \end{vmatrix} = x_{1}y_{2} - y_{1}x_{2} + x_{2}y_{3} - y_{2}x_{3} +x_{3}y_{1} - y_{3}x_{1}

 算法:

  1.  找到最初起点,以Y值最小值为起点,若存在相同Y最小值,则再比较x值偏小的
  2. 以起点作为角排序中心点,然后进行角排序
  3. 对栈顶两元素向量,判断下一个元素是否在左边,在左边,下一个元素入栈,不在左边,栈顶元素出栈。 若栈内元素不足2个,入栈(当连线向右偏移时,说明当前所选的点不是最外包围点)

代码实现:

import com.alibaba.fastjson.JSONObject;
import com.zhmap.jts.CoordinateUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.locationtech.jts.geom.Coordinate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author doubily
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Point {
    private static  final Logger logger = LoggerFactory.getLogger(Point.class);

    private double x;
    private double y;


    public static List<Point> convexHull(List<Point> points) {
        //小于三个点的情况,直接返回
        if (points.size() < 3) {
            return points;
        }

        Point first = points.get(0);
        // 找到最左下角的点
        for (int i = 0; i < points.size(); i++) {
            if (points.get(i).getY() < first.getY() || (points.get(i).getY() == first.getY() && points.get(i).getX() < first.getX())) {
                first = points.get(i);
            }
        }
        logger.info("凸包计算-最下面偏左的点位是:{}", first);
        List<Point> sortedList = angleSorting(first,points);
        logger.info("凸包计算-角排序结果:{}", JSONObject.toJSONString(sortedList));
        int firstIndex = 0;
        for (int i = 0; i < sortedList.size(); i++) {
            if (first.getX() == sortedList.get(i).getX() && first.getY() == sortedList.get(i).getY()) {
                firstIndex = i;
            }
        }
        // 结果集合
        List<Point> convexHullPoints = new ArrayList<>();
        convexHullPoints.add(first);
        int nextIndex = firstIndex + 1 == sortedList.size() ? 0 : firstIndex + 1;
        Point next = new Point();
        while (next != first) {
            next = sortedList.get(nextIndex);

            while(true) {
                if (convexHullPoints.size() < 2) {
                    convexHullPoints.add(next);
                    break;
                }
                if (isEnabled(convexHullPoints.get(convexHullPoints.size()-2), convexHullPoints.get(convexHullPoints.size()-1), next)) {
                    convexHullPoints.add(next);
                    break;
                } else {
                    convexHullPoints.remove(convexHullPoints.size()-1);
                }
            }
            nextIndex = nextIndex + 1 == sortedList.size() ? 0: nextIndex +1;
        }
        convexHullPoints = convexHullPoints.stream().distinct().collect(Collectors.toList());
        logger.info("凸包计算-结果:{}", JSONObject.toJSONString(convexHullPoints));
        logger.info("凸包结算-结果绘制成线:{}", point2WktLine(convexHullPoints));
        return convexHullPoints;
    }

    public static boolean isEnabled(Point A, Point B, Point C) {
        double mulCross =  A.getX()*B.getY() - A.getY()*B.getX() + B.getX()*C.getY() - B.getY()*C.getX() + C.getX()*A.getY() - C.getY()*A.getX();
        if (mulCross >= 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 以原点为中心进行角排序
     * @param points 点位
     * @return 排序的点位
     */
    public static List<Point> angleSorting(Point first, List<Point> points) {
        points = points.stream().sorted(Comparator.comparing(Point::getX)).collect(Collectors.toList());
        points = points.stream().sorted(Comparator.comparing(Point::getY)).collect(Collectors.toList());
        Map<Double, Point> pointMap = new HashMap<>();
        List<Double> angles = new ArrayList<>();
        for (Point point: points) {
            double angle = Math.atan2(point.getY() - first.getY(), point.getX() - first.getX()) * 180.0 / Math.PI;
            if (angle < 0) {
                angle += 360.0;
            }
            pointMap.put(angle, point);
            angles.add(angle);
        }
        angles = angles.stream().sorted().collect(Collectors.toList());
        List<Point> result = new ArrayList<>();
        for (Double angle: angles) {
            result.add(pointMap.get(angle));
        }
        return result;
    }

    public static String point2WktLine(List<Point> points) {
        StringBuilder sb = new StringBuilder("LINESTRING(");
        for (Point point: points) {
            sb.append(point.getX()).append(" ").append(point.getY()).append(", ");
        }
        sb.append(points.get(0).getX()).append(" ").append(points.get(0).getY()).append(")");
        return sb.toString();
    }

    public static List<Point> point2Wgs84(List<Point> points) {
        List<Point> coordinates = new ArrayList<>();
        for (Point point: points) {
            Coordinate coordinate = new Coordinate(point.getX(), point.getY(),0D);
            Coordinate cover = CoordinateUtils.gaussianToWgs84(coordinate, 117);
            coordinates.add(new Point(cover.getX(), cover.getY()));
        }
        return coordinates;
    }

    public static String point2WktPoint(List<Point> points){
        StringBuilder sb = new StringBuilder("MULTIPOINT(");
        for (Point point: points) {
            sb.append(point.getX()).append(" ").append(point.getY()).append(", ");
        }
        sb.append(points.get(0).getX()).append(" ").append(points.get(0).getY()).append(")");
        return sb.toString();
    }
    public static void main(String[] args) {
        List<Point> points = new ArrayList<>();
        points.add(new Point(116.51420419741034,39.72464578059458));
        points.add(new Point(116.5142100494552,39.72461905019859));
        points.add(new Point(116.51421804106533 ,39.724615458020104));
        points.add(new Point(116.51421649139034,39.72462206236937));
        points.add(new Point(116.51422472532401,39.724611537067666));
        points.add(new Point(116.51422189537502,39.72459915116547));
        points.add(new Point(116.51421999329702,39.724606604151624));
        points.add(new Point(116.51420852801604,39.72461304201025));
        points.add(new Point(116.51420145678534,39.72464519351396));

        logger.info("原始点位:{}",point2WktPoint(points));
        logger.info("原始点位连线:{}",point2WktLine(points));
        convexHull(points).stream().distinct().collect(Collectors.toList());
    }
}

实现效果图:

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值