puzhibing
2026-02-04 85bafa86f44fbe6646019fd8ecbab79f552ccd0a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
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);
    }
 
 
}