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 coordinates; private GeoPoint center; private double radius; // 半径(米) private Map 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 getCoordinates() { return coordinates; } public void setCoordinates(List 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 getProperties() { return properties; } public void setProperties(Map 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 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 collection = database.getCollection(collectionName); collection.createIndex(new Document(fieldName, "2d")); System.out.println("创建2d索引成功: " + collectionName + "." + fieldName); } /** * 保存电子围栏到数据库 */ public void saveGeoFence(String collectionName, GeoFence fence) { MongoCollection 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> polygonPositions = new ArrayList<>(); List 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> circlePositions = new ArrayList<>(); List 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 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 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 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 findFencesContainingPoint(GeoPoint point, String collectionName, String geoFieldName) { MongoCollection collection = database.getCollection(collectionName); List fenceIds = new ArrayList<>(); // 查询所有包含该点的围栏 Bson filter = Filters.geoIntersects(geoFieldName, point.toGeoJsonPoint()); collection.find(filter).forEach(doc -> { fenceIds.add( doc.getString("_id")); }); return fenceIds; } public List 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 pointsCollection = database.getCollection(pointsCollectionName); List results = new ArrayList<>(); try (MongoCursor cursor = pointsCollection.find(filter).iterator()) { while (cursor.hasNext()) { results.add(cursor.next()); } } return results; } /** * 获取围栏的几何数据 */ private Document getFenceGeometry(String fenceId, String fencesCollectionName) { MongoCollection fencesCollection = database.getCollection(fencesCollectionName); return fencesCollection.find(Filters.eq("_id", fenceId)).first(); } /** * 方法4:使用聚合管道进行复杂的地理查询 */ public Map analyzePointInFences(GeoPoint point, String collectionName) { MongoCollection collection = database.getCollection(collectionName); List 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 collection = database.getCollection(collectionName); List 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> batchCheckPointsInFences(List points, String collectionName) { MongoCollection collection = database.getCollection(collectionName); Map> result = new HashMap<>(); for (GeoPoint point : points) { Bson filter = Filters.geoIntersects("geometry", point.toGeoJsonPoint()); List fenceNames = new ArrayList<>(); collection.find(filter).forEach(doc -> { fenceNames.add(doc.getString("name")); }); result.put(point, fenceNames); } return result; } /** * 查找与指定点距离在一定范围内的围栏 */ public List findFencesNearPoint(GeoPoint point, double maxDistanceMeters, String collectionName) { MongoCollection collection = database.getCollection(collectionName); // 将米转换为弧度(适用于球面几何) double maxDistanceRadians = maxDistanceMeters / 6378100; // 地球半径 Bson filter = Filters.nearSphere("geometry", point.toGeoJsonPoint(), maxDistanceRadians, 0.0); List 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 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 collection = database.getCollection(collectionName); Document fence = collection.find(Filters.eq("_id", fenceId)).first(); if (fence == null) return false; List center = (List) fence.get("center"); double radius = fence.getDouble("radius"); Bson filter = Filters.geoWithinCenterSphere( "location", // 假设点存储在location字段 center.get(0), // 经度 center.get(1), // 纬度 radius ); // 查找该点是否在圆形内 MongoCollection 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 findPointsByParentId(String parentId, String parentIdField, String collectionName) { MongoCollection collection = database.getCollection(collectionName); List 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 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 fences = findFencesContainingPoint(geoPoint, collectionName, "points"); // 检查查询结果中是否包含指定的fenceId return fences.contains(fenceId); } }