定义:在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包
前置:B点位于向量 的左边, 与 叉积为正
B点位于向量 的右边, 与 叉积为负
算法:
- 找到最初起点,以Y值最小值为起点,若存在相同Y最小值,则再比较x值偏小的
- 以起点作为角排序中心点,然后进行角排序
- 对栈顶两元素向量,判断下一个元素是否在左边,在左边,下一个元素入栈,不在左边,栈顶元素出栈。 若栈内元素不足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());
}
}
实现效果图: