需求
对所有车辆位置数据进行汇总判断是否出现大面积聚集停放车辆(一般指半径XX米内集中停放超过XX天,车辆总数大于XX辆)情况。
实现
- 点聚合算法(直接距离法)
将所有的点转换成一个个聚合簇。遍历所有的点,判断是否有聚合簇可以聚合该点(点和聚合簇中心点之间的距离小于给定的半径),选择相离最近的一个聚合簇进行聚合,若该点不可以被聚合簇进行聚合,则单独作为一个聚合簇。
核心代码:
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;
}
- 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
}
- 网格距离算法
找出所有经纬度中最大和最小的,构成一个正方形,将正方形按照半径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非常耗内存,不建议使用;
推荐
推荐使用直接距离法和网格距离算法