java大面积车辆聚集算法实现

需求

对所有车辆位置数据进行汇总判断是否出现大面积聚集停放车辆(一般指半径XX米内集中停放超过XX天,车辆总数大于XX辆)情况。

实现

  1. 点聚合算法(直接距离法)
    将所有的点转换成一个个聚合簇。遍历所有的点,判断是否有聚合簇可以聚合该点(点和聚合簇中心点之间的距离小于给定的半径),选择相离最近的一个聚合簇进行聚合,若该点不可以被聚合簇进行聚合,则单独作为一个聚合簇。

核心代码:

private double r = 500;

/**
* 缺点:各个点迭代顺序不同导致最终结果会有差异
*/
public void directDistance() {
	// 查询所有需要聚合的坐标
	List<Map<String, Object>> list = userMapper.selectUser3();
	List<Point> points = new ArrayList<>();
	// 将所有坐标转换成对象集合
	list.forEach(data -> points.add(Point.builder().latitude(Double.parseDouble(data.get("lat").toString()))
	        .longitude(Double.parseDouble(data.get("lng").toString())).build()));
	// 聚合簇结果集
	List<Cluster> clusters = new ArrayList<>();
	int count = 0;
	// 遍历所有的点
	for (Point point : points) {
	    // 判断是否有聚合簇可以聚合该点
	    Cluster isCluster = null;
	    for (Cluster cluster : clusters) {
	        // 判断点是否在聚合簇的矩形范围内,如果在的话才进行下一步的距离判断
	        // 优化点:不以圆来判断,就使用矩形进行范围判断
	        if(cluster.getLatLngBounds().contains(point)) {
	            double distance = calculateLineDistance(point, cluster.getCenterPoint());
	            // 判断该点和聚合簇中心点之间距离是否小于指定半径(可能有多个聚合簇满足条件,选择相离最近的一个进行聚合)
	            // 优化点:就以第一个相邻的聚合簇进行聚合
	            if (distance < r && (isCluster == null || calculateLineDistance(point, cluster.getCenterPoint()) <
	                    calculateLineDistance(point, isCluster.getCenterPoint()))) {
	                isCluster = cluster;
	            }
	        }
	        count++;
	    }
	
	    if (isCluster != null) {
	        // 该点可以被聚合簇进行聚合
	        isCluster.addCount();
	        isCluster.getPoints().add(point);
	        // 重置中心点和矩形经纬度范围(比较耗时)
	        // 优化点:就以第一个点的坐标作为中心进行聚合
	        isCluster.setCenterPoint(getCenterPoint(isCluster.getPoints()));
	        isCluster.setLatLngBounds(LatLngBounds.builder()
	                .northeast(Point.builder().latitude(point.getLatitude() + km2Degree(r / 1000))
	                        .longitude(point.getLongitude() + km2Degree(r / 1000)).build())
	                .southwest(Point.builder().latitude(point.getLatitude() - km2Degree(r / 1000))
	                        .longitude(point.getLongitude() - km2Degree(r / 1000)).build()).build());
	    } else {
	        // 该点不可以被聚合簇进行聚合,单独作为一个聚合簇添加到集合中
	        clusters.add(Cluster.builder().centerPoint(point).points(CollUtil.newLinkedList(point)).count(1)
	                .latLngBounds(LatLngBounds.builder()
	                        .northeast(Point.builder().latitude(point.getLatitude() + km2Degree(r / 1000))
	                                .longitude(point.getLongitude() + km2Degree(r / 1000)).build())
	                        .southwest(Point.builder().latitude(point.getLatitude() - km2Degree(r / 1000))
	                                .longitude(point.getLongitude() - km2Degree(r / 1000)).build()).build()).build());
	    }
	}
	
	// 是否出现半径500米内集中停放超过60天且SOC大于80%,车辆总数大于100辆的情况
	int vinCount = 0;
	for (Cluster cluster : clusters) {
	    if(cluster.getPoints().size() > 100){
	        vinCount++;
	    }
	}
}

/**
* 计算两点的距离
*/
private double calculateLineDistance(Point source, Point target) {
   double sourceLongitude = source.getLongitude();
   double sourceLatitude = source.getLatitude();
   double targetLongitude = target.getLongitude();
   double targetLatitude = target.getLatitude();
   sourceLatitude = sourceLatitude * Math.PI / 180.0;
   targetLatitude = targetLatitude * Math.PI / 180.0;
   //地球半径
   double r = 6378137;
   double a = sourceLatitude - targetLatitude;
   double b = (sourceLongitude - targetLongitude) * Math.PI / 180.0;
   double sa2 = Math.sin(a / 2.0);
   double sb2 = Math.sin(b / 2.0);
   return 2 * r * Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(sourceLatitude) * Math.cos(targetLatitude) * sb2 * sb2));
}

/**
* 取多个经纬度的中心点
*/
public Point getCenterPoint(List<Point> pointList) {

   int total = pointList.size();
   double X = 0, Y = 0, Z = 0;

   for (Point point : pointList) {
       double lat, lng, x, y, z;
       lat = point.getLatitude() * Math.PI / 180;
       lng = point.getLongitude() * Math.PI / 180;
       x = Math.cos(lat) * Math.cos(lng);
       y = Math.cos(lat) * Math.sin(lng);
       z = Math.sin(lat);
       X += x;
       Y += y;
       Z += z;
   }

   X = X / total;
   Y = Y / total;
   Z = Z / total;
   double Lon = Math.atan2(Y, X);
   double Hyp = Math.sqrt(X * X + Y * Y);
   double Lat = Math.atan2(Z, Hyp);
   double longitude = Lon * 180 / Math.PI;
   double latitude = Lat * 180 / Math.PI;
   return Point.builder().longitude(longitude).latitude(latitude).build();
}

/**
* 地球半径:km
*/
public final double earthRadius = 6371.393;

/**
* 将公里数转换为度数
*/
public Double km2Degree(Double l) {
   //公式:l(弧长)=degree(圆心角)× π(圆周率)× r(半径)/180
   //转换后的公式:degree(圆心角)=l(弧长) × 180/(π(圆周率)× r(半径))
   return (180 / earthRadius / Math.PI) * l;
}

/**
* 将度数转换为公里数
*/
public Double degree2Km(Double degree) {
   //公式:l(弧长)=degree(圆心角)× π(圆周率)× r(半径)/180
   //将180放在前面,降低数值
   return earthRadius / 180 * Math.PI * degree;
}

实体类:

public class DirectUtil {

    /**
     * 计算两点的距离
     */
    private double calculateLineDistance(Point source, Point target) {
        double sourceLongitude = source.getLongitude();
        double sourceLatitude = source.getLatitude();
        double targetLongitude = target.getLongitude();
        double targetLatitude = target.getLatitude();
        sourceLatitude = sourceLatitude * Math.PI / 180.0;
        targetLatitude = targetLatitude * Math.PI / 180.0;
        //地球半径
        double r = 6378137;
        double a = sourceLatitude - targetLatitude;
        double b = (sourceLongitude - targetLongitude) * Math.PI / 180.0;
        double sa2 = Math.sin(a / 2.0);
        double sb2 = Math.sin(b / 2.0);
        return 2 * r * Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(sourceLatitude) * Math.cos(targetLatitude) * sb2 * sb2));
    }

    /**
     * 取多个经纬度的中心点
     */
    public Point getCenterPoint(List<Point> pointList) {

        int total = pointList.size();
        double X = 0, Y = 0, Z = 0;

        for (Point point : pointList) {
            double lat, lng, x, y, z;
            lat = point.getLatitude() * Math.PI / 180;
            lng = point.getLongitude() * Math.PI / 180;
            x = Math.cos(lat) * Math.cos(lng);
            y = Math.cos(lat) * Math.sin(lng);
            z = Math.sin(lat);
            X += x;
            Y += y;
            Z += z;
        }

        X = X / total;
        Y = Y / total;
        Z = Z / total;
        double Lon = Math.atan2(Y, X);
        double Hyp = Math.sqrt(X * X + Y * Y);
        double Lat = Math.atan2(Z, Hyp);
        double longitude = Lon * 180 / Math.PI;
        double latitude = Lat * 180 / Math.PI;
        return Point.builder().longitude(longitude).latitude(latitude).build();
    }

    /**
     * 地球半径:km
     */
    public final double earthRadius = 6371.393;

    /**
     * 将公里数转换为度数
     */
    public Double km2Degree(Double l) {
        //公式:l(弧长)=degree(圆心角)× π(圆周率)× r(半径)/180
        //转换后的公式:degree(圆心角)=l(弧长) × 180/(π(圆周率)× r(半径))
        return (180 / earthRadius / Math.PI) * l;
    }

    /**
     * 将度数转换为公里数
     */
    public Double degree2Km(Double degree) {
        //公式:l(弧长)=degree(圆心角)× π(圆周率)× r(半径)/180
        //将180放在前面,降低数值
        return earthRadius / 180 * Math.PI * degree;
    }

    /**
     * 判断是否出现车辆聚集的情况
     */
    private boolean directDistance(double r, int vinCount, List<Point> points){
        // 聚合簇结果集
        List<Cluster> clusters = new ArrayList<>();
        // 遍历所有的点
        for (Point point : points) {
            // 判断是否有聚合簇可以聚合该点
            Cluster isCluster = null;
            for (Cluster cluster : clusters) {
                // 判断点是否在聚合簇的矩形范围内,如果在的话才进行下一步的距离判断
                // 优化点:不以圆来判断,就使用矩形进行范围判断
                if(cluster.getLatLngBounds().contains(point)) {
                    double distance = calculateLineDistance(point, cluster.getCenterPoint());
                    // 判断该点和聚合簇中心点之间距离是否小于指定半径(可能有多个聚合簇满足条件,选择相离最近的一个进行聚合)
                    // 优化点:就以第一个相邻的聚合簇进行聚合
                    if (distance < r && (isCluster == null || calculateLineDistance(point, cluster.getCenterPoint()) <
                            calculateLineDistance(point, isCluster.getCenterPoint()))) {
                        isCluster = cluster;
                    }
                }
            }

            if (isCluster != null) {
                // 该点可以被聚合簇进行聚合
                isCluster.addCount();
                isCluster.getPoints().add(point);
                // 重置中心点和矩形经纬度范围(比较耗时)
                // 优化点:就以第一个点的坐标作为中心进行聚合
                isCluster.setCenterPoint(getCenterPoint(isCluster.getPoints()));
                isCluster.setLatLngBounds(LatLngBounds.builder()
                        .northeast(Point.builder().latitude(point.getLatitude() + km2Degree(r / 1000))
                                .longitude(point.getLongitude() + km2Degree(r / 1000)).build())
                        .southwest(Point.builder().latitude(point.getLatitude() - km2Degree(r / 1000))
                                .longitude(point.getLongitude() - km2Degree(r / 1000)).build()).build());
            } else {
                // 该点不可以被聚合簇进行聚合,单独作为一个聚合簇添加到集合中
                clusters.add(Cluster.builder().centerPoint(point).points(CollUtil.newLinkedList(point)).count(1)
                        .latLngBounds(LatLngBounds.builder()
                                .northeast(Point.builder().latitude(point.getLatitude() + km2Degree(r / 1000))
                                        .longitude(point.getLongitude() + km2Degree(r / 1000)).build())
                                .southwest(Point.builder().latitude(point.getLatitude() - km2Degree(r / 1000))
                                        .longitude(point.getLongitude() - km2Degree(r / 1000)).build()).build()).build());
            }
        }
        // 判断是否出现车辆聚集的情况
        for (Cluster cluster : clusters) {
            if(cluster.getPoints().size() > vinCount){
                // 出现聚集
                return true;
            }
        }

        return false;
    }
}
@Data
@Builder
public class Cluster implements Serializable {

    /**
     * 该聚合簇根据中心点及圆半径得到的的矩形经纬度范围
     */
    LatLngBounds latLngBounds;

    /**
     * 该聚合簇圆半径中心
     */
    private Point centerPoint;

    /**
     * 该聚合簇包含的点
     */
    private List<Point> points;

    /**
     * 该聚合簇包含的点个数
     */
    private int count;

    public void addCount() {
        this.count++;
    }
}
@Data
@Builder
public class LatLngBounds {

    /**
     * 西南的经纬度
     */
    private Point southwest;

    /**
     * 东北的经纬度
     */
    private Point northeast;

    public boolean contains(Point point) {
        return this.checkLatitude(point.getLatitude()) && this.checkLongitude(point.getLongitude());
    }

    private boolean checkLatitude(double latitude) {
        return this.southwest.getLatitude() <= latitude && latitude <= this.northeast.getLatitude();
    }

    private boolean checkLongitude(double longitude) {
        return this.southwest.getLongitude() <= longitude && longitude <= this.northeast.getLongitude();
    }
}
@Data
@Builder
public class Point implements Serializable {

    /**
     * 经度
     */
    private double longitude;

    /**
     * 纬度
     */
    private double latitude;
}
  1. DBSCAN算法
    直观效果上看,DBSCAN算法可以找到样本点的全部密集区域,并把这些密集区域当做一个一个的聚类簇。指定领域半径为给定的半径,邻域中数据对象数目阈值为给定的车辆数上限,计算得到所有满足条件的聚类簇。将这些聚类簇的边缘点的经纬度化作一个矩形, 计算这个矩形是小于所给半径圆。若矩形比所给半径圆大,还需对该矩形进行划分判断(使用点聚合算法),是否有某一块满足半径XX米内且车辆总数大于XX辆。(由于DBSCAN算法直接对整个数据集进行操作,并且在聚类之前需要建立相应的R*树,并绘制k-dist图,因此算法所需的内存空间和I/O消耗都相当可观。在计算资源有限而数据量又非常庞大的情况下,DBSCAN算法的效率将受到很大影响。DBSCAN算法将区域查询得到的所有未被处理过的点都作为种子点,留待下一步扩展处理。对于大规模数据集中的较大类而言,这种策略会使种子点的数目不断膨胀,算法所需的内存空间也会快速增加。)

核心代码:

private double r = 1000;

/**
 * 慢,并且非常耗内存,28000的数据就出现内存溢出
 */
public void dbScan() throws Exception {
    // 查询所有需要聚合的坐标
    List<Map<String, Object>> list = userMapper.selectUser3();
    List<CustomerPoint> points = new LinkedList<>();
    // 将所有坐标转换成对象集合
    list.forEach(data -> points.add(new CustomerPoint(new double[]{Double.parseDouble(data.get("lat").toString()),
            Double.parseDouble(data.get("lng").toString())})));

    //这里第一个参数为距离,第二个参数为最小邻居数量
    DBScanUtil db = new DBScanUtil(r, 1);
    //返回结果并打印
    List<List<CustomerPoint>> aa =db.cluster(points);
}

实体类:

public class DBScanUtil {

    /**
     * 邻域半径
     */
    private final double distance;

    /**
     * 邻域中数据对象数目阈值
     */
    private final int minPoints;

    /**
     * 标记每个点的状态
     */
    private final Map<CustomerPoint, PointStatus> visited = new HashMap<>();


    public DBScanUtil(final double distance, final int minPoints)
            throws Exception {
        if (distance < 0.0d) {
            throw new Exception("距离小于0");
        }
        if (minPoints < 0) {
            throw new Exception("点数小于0");
        }
        this.distance = distance;
        this.minPoints = minPoints;
    }

    /**
     * 返回customerPoint的多个聚合
     */
    public List<List<CustomerPoint>> cluster(List<CustomerPoint> points) {

        final List<List<CustomerPoint>> clusters = new ArrayList<>();

        for (CustomerPoint point : points) {
            //如果已经被标记(已被使用非空闲点)
            if (visited.get(point) != null) {
                continue;
            }
            //找出所有从该点密度可达的数据对象点,形成一个簇
            List<CustomerPoint> neighbors = getNeighbors(point, points);
            if (neighbors.size() >= minPoints) {
                //邻域半径R内的点的个数大于最少点数目minpoints,密集
                //更新点标记状态
                visited.put(point, PointStatus.POINT);
                List<CustomerPoint> cluster = new ArrayList<>();
                //遍历所有邻居继续拓展找点
                clusters.add(expandCluster(cluster, point, neighbors, points, visited));
            } else {
                //达不到密集状态,该点为边缘点
                visited.put(point, PointStatus.NOISE);
            }
        }

        return clusters;
    }


    private List<CustomerPoint> expandCluster(List<CustomerPoint> cluster,
                                              CustomerPoint point,
                                              List<CustomerPoint> neighbors,
                                              List<CustomerPoint> points,
                                              Map<CustomerPoint, PointStatus> visited) {
        cluster.add(point);
        //遍历所有的邻居
        for (CustomerPoint current : neighbors) {
            PointStatus pStatus = visited.get(current);
            //该点未被标记,为空闲点,将该点的邻居纳入聚簇范围内
            if (pStatus == null) {
                List<CustomerPoint> currentNeighbors = getNeighbors(current, points);
                neighbors.addAll(currentNeighbors);
            }
            //该点未被其他聚簇纳入,纳入本聚簇,更新点标记状态
            if (pStatus != PointStatus.POINT) {
                visited.put(current, PointStatus.POINT);
                cluster.add(current);
            }
        }
        return cluster;
    }

    //找到所有的邻居
    private List<CustomerPoint> getNeighbors(CustomerPoint point, List<CustomerPoint> points) {
        List<CustomerPoint> neighbors = new ArrayList<>();
        for (CustomerPoint neighbor : points) {
            if (visited.get(neighbor) != null) {
                continue;
            }
            if (point != neighbor && neighbor.distanceFrom(point) <= distance) {
                neighbors.add(neighbor);
            }
        }
        return neighbors;
    }

    //做数据进行测试
    public static void main(String[] args) throws Exception {
        CustomerPoint customerPoint = new CustomerPoint(new double[]{3, 8});
        CustomerPoint customerPoint1 = new CustomerPoint(new double[]{4, 7});
        CustomerPoint customerPoint2 = new CustomerPoint(new double[]{4, 8});
        CustomerPoint customerPoint3 = new CustomerPoint(new double[]{5, 6});
        CustomerPoint customerPoint4 = new CustomerPoint(new double[]{3, 9});
        CustomerPoint customerPoint5 = new CustomerPoint(new double[]{5, 1});
        CustomerPoint customerPoint6 = new CustomerPoint(new double[]{5, 2});
        CustomerPoint customerPoint7 = new CustomerPoint(new double[]{6, 3});
        CustomerPoint customerPoint8 = new CustomerPoint(new double[]{7, 3});
        CustomerPoint customerPoint9 = new CustomerPoint(new double[]{7, 4});
        CustomerPoint customerPoint10 = new CustomerPoint(new double[]{0, 2});
        CustomerPoint customerPoint11 = new CustomerPoint(new double[]{8, 16});
        CustomerPoint customerPoint12 = new CustomerPoint(new double[]{1, 1});
        CustomerPoint customerPoint13 = new CustomerPoint(new double[]{1, 3});

        List<CustomerPoint> cs = new ArrayList<>();
        cs.add(customerPoint13);
        cs.add(customerPoint12);
        cs.add(customerPoint11);
        cs.add(customerPoint10);
        cs.add(customerPoint9);
        cs.add(customerPoint8);
        cs.add(customerPoint7);
        cs.add(customerPoint6);
        cs.add(customerPoint5);
        cs.add(customerPoint4);
        cs.add(customerPoint3);
        cs.add(customerPoint2);
        cs.add(customerPoint1);
        cs.add(customerPoint);
        //这里第一个参数为距离,第二个参数为最小邻居数量
        DBScanUtil db = new DBScanUtil(1.5, 1);
        //返回结果并打印
        List<List<CustomerPoint>> aa = db.cluster(cs);
        for (int i = 0; i < aa.size(); i++) {
            for (int j = 0; j < aa.get(i).size(); j++) {
                System.out.print(aa.get(i).get(j).toString());
            }
            System.out.println();
        }
    }
}
public class CustomerPoint implements Clusterable<CustomerPoint> {

    private final double[] point;

    public CustomerPoint(final double[] point) {
        this.point = point;
    }

    public double[] getPoint() {
        return point;
    }

    public double distanceFrom(final CustomerPoint p) {
        return MathUtils.distance(point, p.getPoint());
    }

    public CustomerPoint centroidOf(final Collection<CustomerPoint> points) {
        double[] centroid = new double[getPoint().length];
        for (CustomerPoint p : points) {
            for (int i = 0; i < centroid.length; i++) {
                centroid[i] += p.getPoint()[i];
            }
        }
        for (int i = 0; i < centroid.length; i++) {
            centroid[i] /= points.size();
        }
        return new CustomerPoint(centroid);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(point);
    }

    @Override
    public boolean equals(final Object other) {
        if (!(other instanceof CustomerPoint)) {
            return false;
        }
        final double[] otherPoint = ((CustomerPoint) other).getPoint();
        if (point.length != otherPoint.length) {
            return false;
        }
        for (int i = 0; i < point.length; i++) {
            if (point[i] != otherPoint[i]) {
                return false;
            }
        }
        return true;
    }
    @Override
    public String toString() {
        final StringBuffer buff = new StringBuffer("{");
        final double[] coordinates = getPoint();
        buff.append("lat:"+coordinates[0]+",");
        buff.append("lng:"+coordinates[1]+",");
        buff.append("}");
        return buff.toString();
    }
}
public enum PointStatus {

    //点的标记,point:聚簇内的点,noise:噪音点
    NOISE, POINT
}
  1. 网格距离算法
    找出所有经纬度中最大和最小的,构成一个正方形,将正方形按照半径XX进行切割,根据每个落在不同正方形中的点算出该正方形的质心,遍历这些质心,对于相邻方格内间距小于半径XX的质心进行合并,然后判断质心所含的点是否出现车辆聚集情况。
    核心代码:
private double r = 500;

/**
* 缺点:合并质心的时候可能出现极端点情况
*/
public void gridDistance() {
	// 查询所有需要聚合的坐标
	List<Map<String, Object>> list = userMapper.selectUser3();
	List<Point> points = new ArrayList<>();
	// 将所有坐标转换成对象集合
	list.forEach(data -> points.add(Point.builder().latitude(Double.parseDouble(data.get("lat").toString()))
	        .longitude(Double.parseDouble(data.get("lng").toString())).build()));
	Point point = new Point(0, 0, 0, 0);
	// 根据最大最下经纬度获取矩形
	Rectangle rectangle = new Rectangle(points.stream().max(Comparator.comparing(Point::getLongitude)).orElse(point).getLongitude(),
	        points.stream().min(Comparator.comparing(Point::getLongitude)).orElse(point).getLongitude(),
	        points.stream().max(Comparator.comparing(Point::getLatitude)).orElse(point).getLatitude(),
	        points.stream().min(Comparator.comparing(Point::getLatitude)).orElse(point).getLatitude());
	// 网格横纵坐标及对应的网格集合
	Map<String, ShortRectangle> shortRectangles = new HashMap<>();
	// 遍历坐标点,计算横纵坐标
	points.forEach(item -> {
	    item.setWide((int) Math.floor((item.getLongitude() - rectangle.getMinLongitude()) / km2Degree(r / 1000)));
	    item.setLength((int) Math.floor((item.getLatitude() - rectangle.getMinLatitude()) / km2Degree(r / 1000)));
	    // 网格横纵坐标
	    String coordinate = item.getLength() + "-" + item.getWide();
	    if (shortRectangles.get(coordinate) == null) {
	        // 如果网格集合中还没有该网格的数据
	        shortRectangles.put(coordinate, ShortRectangle.builder().length(item.getLength()).wide(item.getWide())
	                .count(1).points(ListUtil.toLinkedList(item)).build());
	    } else {
	        // 更新网格数据
	        ShortRectangle shortRectangle = shortRectangles.get(coordinate);
	        shortRectangle.addCount();
	        shortRectangle.getPoints().add(item);
	        shortRectangles.put(coordinate, shortRectangle);
	    }
	});
	// 遍历网格集合,计算质心
	List<ShortRectangle> data = CollUtil.newArrayList(shortRectangles.values());
	data.forEach(item -> item.setCentroid(getCenterPoint(item.getPoints())));
	// 遍历质心,判断是否出现车辆聚集的情况
	int vinCount = 0;
	for (ShortRectangle shortRectangle : data) {
	    // 聚集车辆数
	    int num = shortRectangle.getCount();
	    for (ShortRectangle shortRectangle2 : data) {
	        // 判断是否是相邻网格的质心
	        int lengthAbs = Math.abs(shortRectangle2.getLength() - shortRectangle.getLength());
	        int wideAbs = Math.abs(shortRectangle2.getWide() - shortRectangle.getWide());
	        double v = Math.pow(lengthAbs, 2) + Math.pow(wideAbs, 2);
	        if (v >= 1 && v <= 2 && calculateLineDistance(shortRectangle.getCentroid(), shortRectangle2.getCentroid()) < r) {
	            num += shortRectangle2.getCount();
	        }
	    }
	    if (num > 100) {
	        // 出现聚集
	        vinCount++;
	    }
	}
}

/**
 * 取多个经纬度的中心点
 */
public Point getCenterPoint(List<Point> pointList) {

    int total = pointList.size();
    double X = 0, Y = 0, Z = 0;

    for (Point point : pointList) {
        double lat, lng, x, y, z;
        lat = point.getLatitude() * Math.PI / 180;
        lng = point.getLongitude() * Math.PI / 180;
        x = Math.cos(lat) * Math.cos(lng);
        y = Math.cos(lat) * Math.sin(lng);
        z = Math.sin(lat);
        X += x;
        Y += y;
        Z += z;
    }

    X = X / total;
    Y = Y / total;
    Z = Z / total;
    double Lon = Math.atan2(Y, X);
    double Hyp = Math.sqrt(X * X + Y * Y);
    double Lat = Math.atan2(Z, Hyp);
    double longitude = Lon * 180 / Math.PI;
    double latitude = Lat * 180 / Math.PI;
    return Point.builder().longitude(longitude).latitude(latitude).build();
}

/**
 * 计算两点的距离
 */
private double calculateLineDistance(Point source, Point target) {
    double sourceLongitude = source.getLongitude();
    double sourceLatitude = source.getLatitude();
    double targetLongitude = target.getLongitude();
    double targetLatitude = target.getLatitude();
    sourceLatitude = sourceLatitude * Math.PI / 180.0;
    targetLatitude = targetLatitude * Math.PI / 180.0;
    //地球半径
    double r = 6378137;
    double a = sourceLatitude - targetLatitude;
    double b = (sourceLongitude - targetLongitude) * Math.PI / 180.0;
    double sa2 = Math.sin(a / 2.0);
    double sb2 = Math.sin(b / 2.0);
    return 2 * r * Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(sourceLatitude) * Math.cos(targetLatitude) * sb2 * sb2));
}

/**
 * 地球半径:km
 */
public final double earthRadius = 6371.393;

/**
 * 将公里数转换为度数
 */
public Double km2Degree(Double l) {
    //公式:l(弧长)=degree(圆心角)× π(圆周率)× r(半径)/180
    //转换后的公式:degree(圆心角)=l(弧长) × 180/(π(圆周率)× r(半径))
    return (180 / earthRadius / Math.PI) * l;
}

/**
 * 将度数转换为公里数
 */
public Double degree2Km(Double degree) {
    //公式:l(弧长)=degree(圆心角)× π(圆周率)× r(半径)/180
    //将180放在前面,降低数值
    return earthRadius / 180 * Math.PI * degree;
}

实体类:

public class GridUtil {

    /**
     * 地球半径:km
     */
    private final double earthRadius = 6371.393;

    /**
     * 将公里数转换为度数
     */
    private Double km2Degree(Double l) {
        //公式:l(弧长)=degree(圆心角)× π(圆周率)× r(半径)/180
        //转换后的公式:degree(圆心角)=l(弧长) × 180/(π(圆周率)× r(半径))
        return (180 / earthRadius / Math.PI) * l;
    }

    /**
     * 将度数转换为公里数
     */
    private Double degree2Km(Double degree) {
        //公式:l(弧长)=degree(圆心角)× π(圆周率)× r(半径)/180
        //将180放在前面,降低数值
        return earthRadius / 180 * Math.PI * degree;
    }

    /**
     * 取多个经纬度的中心点
     */
    private Point getCenterPoint(List<Point> pointList) {

        int total = pointList.size();
        double X = 0, Y = 0, Z = 0;

        for (Point point : pointList) {
            double lat, lng, x, y, z;
            lat = point.getLatitude() * Math.PI / 180;
            lng = point.getLongitude() * Math.PI / 180;
            x = Math.cos(lat) * Math.cos(lng);
            y = Math.cos(lat) * Math.sin(lng);
            z = Math.sin(lat);
            X += x;
            Y += y;
            Z += z;
        }

        X = X / total;
        Y = Y / total;
        Z = Z / total;
        double Lon = Math.atan2(Y, X);
        double Hyp = Math.sqrt(X * X + Y * Y);
        double Lat = Math.atan2(Z, Hyp);
        double longitude = Lon * 180 / Math.PI;
        double latitude = Lat * 180 / Math.PI;
        return Point.builder().longitude(longitude).latitude(latitude).build();
    }

    /**
     * 计算两点的距离
     */
    private double calculateLineDistance(Point source, Point target) {
        double sourceLongitude = source.getLongitude();
        double sourceLatitude = source.getLatitude();
        double targetLongitude = target.getLongitude();
        double targetLatitude = target.getLatitude();
        sourceLatitude = sourceLatitude * Math.PI / 180.0;
        targetLatitude = targetLatitude * Math.PI / 180.0;
        //地球半径
        double r = 6378137;
        double a = sourceLatitude - targetLatitude;
        double b = (sourceLongitude - targetLongitude) * Math.PI / 180.0;
        double sa2 = Math.sin(a / 2.0);
        double sb2 = Math.sin(b / 2.0);
        return 2 * r * Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(sourceLatitude) * Math.cos(targetLatitude) * sb2 * sb2));
    }

    /**
     * 判断是否出现车辆聚集的情况
     */
    private boolean gridDistance(double r, int vinCount, List<Point> points){
        Point point = new Point(0, 0, 0, 0);
        // 根据最大最下经纬度获取矩形
        Rectangle rectangle = new Rectangle(points.stream().max(Comparator.comparing(Point::getLongitude)).orElse(point).getLongitude(),
                points.stream().min(Comparator.comparing(Point::getLongitude)).orElse(point).getLongitude(),
                points.stream().max(Comparator.comparing(Point::getLatitude)).orElse(point).getLatitude(),
                points.stream().min(Comparator.comparing(Point::getLatitude)).orElse(point).getLatitude());
        // 网格横纵坐标及对应的网格集合
        Map<String, ShortRectangle> shortRectangles = new HashMap<>();
        // 遍历坐标点,计算横纵坐标
        points.forEach(item -> {
            item.setWide((int) Math.floor((item.getLongitude() - rectangle.getMinLongitude()) / km2Degree(r / 1000)));
            item.setLength((int) Math.floor((item.getLatitude() - rectangle.getMinLatitude()) / km2Degree(r / 1000)));
            // 网格横纵坐标
            String coordinate = item.getLength() + "-" + item.getWide();
            if (shortRectangles.get(coordinate) == null) {
                // 如果网格集合中还没有该网格的数据
                shortRectangles.put(coordinate, ShortRectangle.builder().length(item.getLength()).wide(item.getWide())
                        .count(1).points(ListUtil.toLinkedList(item)).build());
            } else {
                // 更新网格数据
                ShortRectangle shortRectangle = shortRectangles.get(coordinate);
                shortRectangle.addCount();
                shortRectangle.getPoints().add(item);
                shortRectangles.put(coordinate, shortRectangle);
            }
        });
        // 遍历网格集合,计算质心
        List<ShortRectangle> data = CollUtil.newArrayList(shortRectangles.values());
        data.forEach(item -> item.setCentroid(getCenterPoint(item.getPoints())));
        // 遍历质心,判断是否出现车辆聚集的情况
        for (ShortRectangle shortRectangle : data) {
            // 聚集车辆数
            int num = shortRectangle.getCount();
            for (ShortRectangle shortRectangle2 : data) {
                // 判断是否是相邻网格的质心
                int lengthAbs = Math.abs(shortRectangle2.getLength() - shortRectangle.getLength());
                int wideAbs = Math.abs(shortRectangle2.getWide() - shortRectangle.getWide());
                double v = Math.pow(lengthAbs, 2) + Math.pow(wideAbs, 2);
                if (v >= 1 && v <= 2 && calculateLineDistance(shortRectangle.getCentroid(), shortRectangle2.getCentroid()) < r) {
                    num += shortRectangle2.getCount();
                }
            }
            if (num > vinCount) {
                // 出现聚集
                return true;
            }
        }

        return false;
    }
}
@Data
@Builder
public class Point implements Serializable {

    /**
     * 经度
     */
    private double longitude;

    /**
     * 纬度
     */
    private double latitude;

    /**
     * 位于矩形的横坐标
     */
    private int length;

    /**
     * 位于矩形的纵坐标
     */
    private int wide;
}
@Data
@AllArgsConstructor
public class Rectangle {

    private double maxLongitude;

    private double minLongitude;

    private double maxLatitude;

    private double minLatitude;
}
@Data
@Builder
public class ShortRectangle {

    /**
     * 小矩形的横坐标
     */
    private int length;

    /**
     * 小矩形的纵坐标
     */
    private int wide;

    /**
     * 该小矩形包含的点个数
     */
    private int count;

    /**
     * 该小矩形包含的点
     */
    private List<Point> points;

    /**
     * 质心
     */
    private Point centroid;

    public void addCount() {
        count++;
    }
}

性能测试

半径越大,点数越多,网格距离法性能越好;
半径越小,点数越少,直接距离法性能越好;
DbScan非常耗内存,不建议使用;

推荐

推荐使用直接距离法和网格距离算法

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值