package com.xiaopeng.map.utils.mangoUtils;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.mongodb.client.*;
|
import com.mongodb.client.model.Filters;
|
import com.mongodb.client.model.geojson.Point;
|
import com.mongodb.client.model.geojson.Position;
|
import org.bson.Document;
|
import org.bson.conversions.Bson;
|
|
import java.util.*;
|
|
/**
|
* MongoDB地理空间查询工具类
|
*/
|
public class MongoGeoFenceUtil {
|
|
private final MongoClient mongoClient;
|
private final MongoDatabase database;
|
private final ObjectMapper objectMapper;
|
|
/**
|
* 地理坐标点
|
*/
|
public static class GeoPoint {
|
private final double longitude;
|
private final double latitude;
|
|
public GeoPoint(double longitude, double latitude) {
|
this.longitude = longitude;
|
this.latitude = latitude;
|
}
|
|
public double getLongitude() {
|
return longitude;
|
}
|
|
public double getLatitude() {
|
return latitude;
|
}
|
|
public Position toPosition() {
|
return new Position(longitude, latitude);
|
}
|
|
public Point toGeoJsonPoint() {
|
return new Point(new Position(longitude, latitude));
|
}
|
|
public Document toDocument() {
|
return new Document()
|
.append("type", "Point")
|
.append("coordinates", Arrays.asList(longitude, latitude));
|
}
|
|
@Override
|
public String toString() {
|
return String.format("[%.6f, %.6f]", longitude, latitude);
|
}
|
}
|
|
/**
|
* 电子围栏类型
|
*/
|
public enum FenceType {
|
CIRCLE, // 圆形
|
POLYGON, // 多边形
|
LINESTRING, // 线形
|
MULTI_POLYGON // 多个多边形
|
}
|
|
/**
|
* 电子围栏
|
*/
|
public static class GeoFence {
|
private String id;
|
private String name;
|
private FenceType type;
|
private List<GeoPoint> coordinates;
|
private GeoPoint center;
|
private double radius; // 半径(米)
|
private Map<String, Object> properties;
|
|
public GeoFence(String id, String name, FenceType type) {
|
this.id = id;
|
this.name = name;
|
this.type = type;
|
this.coordinates = new ArrayList<>();
|
this.properties = new HashMap<>();
|
}
|
|
// Getters and Setters
|
public String getId() {
|
return id;
|
}
|
|
public void setId(String id) {
|
this.id = id;
|
}
|
|
public String getName() {
|
return name;
|
}
|
|
public void setName(String name) {
|
this.name = name;
|
}
|
|
public FenceType getType() {
|
return type;
|
}
|
|
public void setType(FenceType type) {
|
this.type = type;
|
}
|
|
public List<GeoPoint> getCoordinates() {
|
return coordinates;
|
}
|
|
public void setCoordinates(List<GeoPoint> coordinates) {
|
this.coordinates = coordinates;
|
}
|
|
public GeoPoint getCenter() {
|
return center;
|
}
|
|
public void setCenter(GeoPoint center) {
|
this.center = center;
|
}
|
|
public double getRadius() {
|
return radius;
|
}
|
|
public void setRadius(double radius) {
|
this.radius = radius;
|
}
|
|
public Map<String, Object> getProperties() {
|
return properties;
|
}
|
|
public void setProperties(Map<String, Object> properties) {
|
this.properties = properties;
|
}
|
|
public void addCoordinate(GeoPoint point) {
|
this.coordinates.add(point);
|
}
|
|
public void addCoordinate(double lng, double lat) {
|
this.coordinates.add(new GeoPoint(lng, lat));
|
}
|
|
public void addProperty(String key, Object value) {
|
this.properties.put(key, value);
|
}
|
}
|
|
public MongoGeoFenceUtil(String connectionString, String databaseName) {
|
this.mongoClient = MongoClients.create(connectionString);
|
this.database = mongoClient.getDatabase(databaseName);
|
this.objectMapper = new ObjectMapper();
|
}
|
|
/**
|
* 创建地理空间索引
|
*/
|
public void create2dSphereIndex(String collectionName, String fieldName) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
collection.createIndex(new Document(fieldName, "2dsphere"));
|
System.out.println("创建2dsphere索引成功: " + collectionName + "." + fieldName);
|
}
|
|
/**
|
* 创建2d索引(平面坐标)
|
*/
|
public void create2dIndex(String collectionName, String fieldName) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
collection.createIndex(new Document(fieldName, "2d"));
|
System.out.println("创建2d索引成功: " + collectionName + "." + fieldName);
|
}
|
|
/**
|
* 保存电子围栏到数据库
|
*/
|
public void saveGeoFence(String collectionName, GeoFence fence) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
|
Document geometry = createGeometryDocument(fence);
|
|
Document fenceDoc = new Document()
|
.append("_id", fence.getId())
|
.append("name", fence.getName())
|
.append("type", fence.getType().name())
|
.append("geometry", geometry)
|
.append("properties", fence.getProperties())
|
.append("createdAt", new Date())
|
.append("updatedAt", new Date());
|
|
// 添加中心点用于快速查询
|
if (fence.getCenter() != null) {
|
fenceDoc.append("center", fence.getCenter().toDocument());
|
}
|
|
if (fence.getRadius() > 0) {
|
fenceDoc.append("radius", fence.getRadius());
|
}
|
|
collection.insertOne(fenceDoc);
|
System.out.println("保存围栏成功: " + fence.getName());
|
}
|
|
/**
|
* 创建几何图形文档
|
*/
|
private Document createGeometryDocument(GeoFence fence) {
|
Document geometry = new Document();
|
|
switch (fence.getType()) {
|
case POLYGON:
|
geometry.append("type", "Polygon");
|
List<List<Position>> polygonPositions = new ArrayList<>();
|
List<Position> positions = new ArrayList<>();
|
|
for (GeoPoint point : fence.getCoordinates()) {
|
positions.add(point.toPosition());
|
}
|
// 闭合多边形:最后一个点等于第一个点
|
if (!positions.isEmpty() && !positions.get(0).equals(positions.get(positions.size() - 1))) {
|
positions.add(positions.get(0));
|
}
|
|
polygonPositions.add(positions);
|
geometry.append("coordinates", polygonPositions);
|
break;
|
|
case CIRCLE:
|
// MongoDB原生不支持圆形,使用多边形近似
|
geometry.append("type", "Polygon");
|
List<List<Position>> circlePositions = new ArrayList<>();
|
List<Position> circlePoints = new ArrayList<>();
|
|
int segments = 36; // 36边形近似圆形
|
for (int i = 0; i < segments; i++) {
|
double angle = 2 * Math.PI * i / segments;
|
double lat = fence.getCenter().getLatitude() +
|
(fence.getRadius() / 111320.0) * Math.cos(angle); // 1度≈111320米
|
double lng = fence.getCenter().getLongitude() +
|
(fence.getRadius() / (111320.0 * Math.cos(Math.toRadians(fence.getCenter().getLatitude())))) * Math.sin(angle);
|
|
circlePoints.add(new Position(lng, lat));
|
}
|
// 闭合
|
if (!circlePoints.isEmpty()) {
|
circlePoints.add(circlePoints.get(0));
|
}
|
|
circlePositions.add(circlePoints);
|
geometry.append("coordinates", circlePositions);
|
break;
|
|
case LINESTRING:
|
geometry.append("type", "LineString");
|
List<Position> linePositions = new ArrayList<>();
|
for (GeoPoint point : fence.getCoordinates()) {
|
linePositions.add(point.toPosition());
|
}
|
geometry.append("coordinates", linePositions);
|
break;
|
|
default:
|
throw new IllegalArgumentException("不支持的围栏类型: " + fence.getType());
|
}
|
|
return geometry;
|
}
|
|
/**
|
* 方法1:使用$geoWithin判断点是否在多边形内
|
*/
|
public boolean isPointInPolygonGeoWithin(GeoPoint point, String fenceId, String collectionName) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
|
// 获取围栏的多边形数据
|
Document fence = collection.find(Filters.eq("_id", fenceId)).first();
|
if (fence == null) {
|
throw new IllegalArgumentException("围栏不存在: " + fenceId);
|
}
|
|
// 创建查询:点是否在多边形内
|
Bson filter = Filters.and(
|
Filters.eq("_id", fenceId),
|
Filters.geoWithin(
|
"geometry",
|
point.toGeoJsonPoint()
|
)
|
);
|
|
long count = collection.countDocuments(filter);
|
return count > 0;
|
}
|
|
/**
|
* 方法2:使用$geoIntersects判断点是否与几何图形相交
|
*/
|
public boolean isPointInPolygonGeoIntersects(GeoPoint point, String fenceId, String collectionName) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
|
Bson filter = Filters.and(
|
Filters.eq("_id", fenceId),
|
Filters.geoIntersects("geometry", point.toGeoJsonPoint())
|
);
|
|
long count = collection.countDocuments(filter);
|
return count > 0;
|
}
|
|
|
|
/**
|
* 方法3:查找包含指定点的所有围栏(支持自定义地理字段名称)
|
*/
|
public List<String> findFencesContainingPoint(GeoPoint point, String collectionName, String geoFieldName) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
List<String> fenceIds = new ArrayList<>();
|
|
// 查询所有包含该点的围栏
|
Bson filter = Filters.geoIntersects(geoFieldName, point.toGeoJsonPoint());
|
|
collection.find(filter).forEach(doc -> {
|
fenceIds.add( doc.getString("_id"));
|
});
|
|
return fenceIds;
|
}
|
|
|
public List<Document> findPointsInFence(String fenceId,
|
String pointsCollectionName,
|
String fencesCollectionName) {
|
// 1. 从围栏集合获取围栏的几何数据
|
Document fence = getFenceGeometry(fenceId, fencesCollectionName);
|
if (fence == null) {
|
throw new IllegalArgumentException("围栏不存在: " + fenceId);
|
}
|
|
// 2. 构建地理空间查询
|
Bson filter = Filters.geoWithin("location", fence.get("geometry", Document.class));
|
|
// 3. 执行查询
|
MongoCollection<Document> pointsCollection = database.getCollection(pointsCollectionName);
|
List<Document> results = new ArrayList<>();
|
|
try (MongoCursor<Document> cursor = pointsCollection.find(filter).iterator()) {
|
while (cursor.hasNext()) {
|
results.add(cursor.next());
|
}
|
}
|
|
return results;
|
}
|
/**
|
* 获取围栏的几何数据
|
*/
|
private Document getFenceGeometry(String fenceId, String fencesCollectionName) {
|
MongoCollection<Document> fencesCollection = database.getCollection(fencesCollectionName);
|
return fencesCollection.find(Filters.eq("_id", fenceId)).first();
|
}
|
|
|
/**
|
* 方法4:使用聚合管道进行复杂的地理查询
|
*/
|
public Map<String, Object> analyzePointInFences(GeoPoint point, String collectionName) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
|
List<Document> pipeline = Arrays.asList(
|
// 阶段1:筛选包含该点的围栏
|
new Document("$match",
|
new Document("geometry",
|
new Document("$geoIntersects",
|
new Document("$geometry", point.toDocument())
|
)
|
)
|
),
|
// 阶段2:投影所需字段
|
new Document("$project",
|
new Document("_id", 1)
|
.append("name", 1)
|
.append("type", 1)
|
.append("properties", 1)
|
.append("area", 1)
|
),
|
// 阶段3:统计
|
new Document("$group",
|
new Document("_id", null)
|
.append("totalFences", new Document("$sum", 1))
|
.append("fences", new Document("$push",
|
new Document("id", "$_id")
|
.append("name", "$name")
|
.append("type", "$type")
|
))
|
)
|
);
|
|
Document result = collection.aggregate(pipeline).first();
|
if (result != null) {
|
return result;
|
}
|
|
return new Document("totalFences", 0)
|
.append("fences", new ArrayList<>());
|
}
|
|
/**
|
* 方法5:查找距离点最近的围栏
|
*/
|
public Document findNearestFence(GeoPoint point, String collectionName, int maxDistance) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
|
List<Document> pipeline = Arrays.asList(
|
// 阶段1:添加距离字段
|
new Document("$geoNear",
|
new Document("near", point.toDocument())
|
.append("distanceField", "distance")
|
.append("spherical", true)
|
.append("maxDistance", maxDistance)
|
.append("query", new Document("type", "Polygon")) // 只查询多边形
|
.append("key", "geometry")
|
),
|
// 阶段2:按距离排序
|
new Document("$sort", new Document("distance", 1)),
|
// 阶段3:限制返回数量
|
new Document("$limit", 1)
|
);
|
|
return collection.aggregate(pipeline).first();
|
}
|
|
|
/**
|
* 方法7:批量判断多个点是否在围栏内
|
*/
|
public Map<GeoPoint, List<String>> batchCheckPointsInFences(List<GeoPoint> points, String collectionName) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
Map<GeoPoint, List<String>> result = new HashMap<>();
|
|
for (GeoPoint point : points) {
|
Bson filter = Filters.geoIntersects("geometry", point.toGeoJsonPoint());
|
List<String> fenceNames = new ArrayList<>();
|
|
collection.find(filter).forEach(doc -> {
|
fenceNames.add(doc.getString("name"));
|
});
|
|
result.put(point, fenceNames);
|
}
|
|
return result;
|
}
|
|
|
/**
|
* 查找与指定点距离在一定范围内的围栏
|
*/
|
public List<Document> findFencesNearPoint(GeoPoint point, double maxDistanceMeters, String collectionName) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
|
// 将米转换为弧度(适用于球面几何)
|
double maxDistanceRadians = maxDistanceMeters / 6378100; // 地球半径
|
|
Bson filter = Filters.nearSphere("geometry", point.toGeoJsonPoint(), maxDistanceRadians, 0.0);
|
|
List<Document> results = new ArrayList<>();
|
collection.find(filter).into(results);
|
|
return results;
|
}
|
|
/**
|
* 创建圆形围栏(使用$centerSphere)
|
*/
|
public void createCircleFenceUsingCenterSphere(String fenceId, String name, GeoPoint center,
|
double radiusMeters, String collectionName) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
|
// 将米转换为弧度
|
double radiusRadians = radiusMeters / 6378100;
|
|
Document fenceDoc = new Document()
|
.append("_id", fenceId)
|
.append("name", name)
|
.append("type", "Circle")
|
.append("center", Arrays.asList(center.getLongitude(), center.getLatitude()))
|
.append("radius", radiusRadians) // 以弧度存储
|
.append("radiusMeters", radiusMeters)
|
.append("geometry", new Document()
|
.append("type", "Point")
|
.append("coordinates", Arrays.asList(center.getLongitude(), center.getLatitude())))
|
.append("createdAt", new Date());
|
|
collection.insertOne(fenceDoc);
|
}
|
|
/**
|
* 使用$centerSphere查询圆形区域内的点
|
*/
|
public boolean isPointInCircleUsingCenterSphere(GeoPoint point, String fenceId, String collectionName) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
|
Document fence = collection.find(Filters.eq("_id", fenceId)).first();
|
if (fence == null) return false;
|
|
List<Double> center = (List<Double>) fence.get("center");
|
double radius = fence.getDouble("radius");
|
|
Bson filter = Filters.geoWithinCenterSphere(
|
"location", // 假设点存储在location字段
|
center.get(0), // 经度
|
center.get(1), // 纬度
|
radius
|
);
|
|
// 查找该点是否在圆形内
|
MongoCollection<Document> pointsCollection = database.getCollection("tracking_points");
|
Document pointDoc = pointsCollection.find(
|
Filters.and(
|
Filters.eq("location.coordinates",
|
Arrays.asList(point.getLongitude(), point.getLatitude())),
|
filter
|
)
|
).first();
|
|
return pointDoc != null;
|
}
|
|
/**
|
* 关闭连接
|
*/
|
public void close() {
|
if (mongoClient != null) {
|
mongoClient.close();
|
}
|
}
|
|
|
/**
|
* 查找指定父围栏ID的禁行区域点位
|
* @param parentId 父围栏ID
|
* @param parentIdField 父围栏ID字段名
|
* @param collectionName 集合名称
|
* @return 禁行区域点位列表
|
*/
|
public List<Document> findPointsByParentId(String parentId, String parentIdField, String collectionName) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
List<Document> points = new ArrayList<>();
|
|
// 查询父围栏ID等于指定值的所有文档
|
Bson filter = Filters.eq(parentIdField, parentId);
|
|
collection.find(filter).forEach(doc -> {
|
points.add(doc);
|
});
|
|
return points;
|
}
|
|
/**
|
* 根据围栏ID获取电子围栏点位
|
* @param fenceId 围栏ID
|
* @param collectionName 集合名称
|
* @return 电子围栏点位
|
*/
|
public Document getFenceById(String fenceId, String collectionName) {
|
MongoCollection<Document> collection = database.getCollection(collectionName);
|
return collection.find(Filters.eq("_id", fenceId)).first();
|
}
|
|
|
|
/**
|
* 根据经纬度点位判断是否在指定的电子围栏中
|
* @param longitude 经度
|
* @param latitude 纬度
|
* @param fenceId 围栏ID
|
* @param collectionName 集合名称
|
* @return 是否在围栏中
|
*/
|
public boolean isInFence(double longitude, double latitude, String fenceId, String collectionName) {
|
// 创建GeoPoint对象
|
MongoGeoFenceUtil.GeoPoint geoPoint = new MongoGeoFenceUtil.GeoPoint(longitude, latitude);
|
|
// 调用MongoGeoFenceUtil的方法查询包含该点的围栏
|
List<String> fences = findFencesContainingPoint(geoPoint, collectionName, "points");
|
|
// 检查查询结果中是否包含指定的fenceId
|
return fences.contains(fenceId);
|
}
|
|
|
}
|