引言

https://mongodb.net.cn/manual/geospatial-queries/

image-20220401110849399

空间查询——查Point元素

查询字段的类型
1
2
// @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
GeoJsonPoint center;  // 图像中心点
构造box参数

box格式 [左下,然后右上]

1
Box box = new Box(new Point(-38, -42), new Point(162, 63));

Point注意导入的是geo下的

image-20211214153549566

使用mongoRepository方式查询
1
2
3
public interface MapItemDao extends MongoRepository<MapItem, String>{
    Page<MapItem> findByCenterWithin(Box box, Pageable pageable);
}
使用mongoTemplate方式查询
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/**
 * 矩形查询
 * box格式 [左下,然后右上]
 * <code>Box box = new Box(new Point(-38, -42), new Point(162, 63));</code>
 * @param box
 * @return java.util.List<com.example.maparchivebackend.entity.po.MapItem>
 * @Author bin
 **/
public List<MapItem> findInPolygon(Box box) {

    Query query = new Query();
    query.addCriteria(Criteria.where("center").within(box));
    return mongoTemplate.find(query, MapItem.class, "mapItem");

}
使用BasicDBObject方式查询
 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
public List<DBObject> withinBox(Point bottomLeft, Point upperRight, BasicDBObject query, FindDTO findDTO) {
    if (query == null)
        query = new BasicDBObject();

    LinkedList<double[]> box = new LinkedList<>();
    box.add(new double[]{bottomLeft.getLng(), bottomLeft.getLat()});
    box.add(new double[]{upperRight.getLng(), upperRight.getLat()});


    query.put("center", new BasicDBObject("$geoWithin", new BasicDBObject("$box", box)));

    // 查询的时候从第几个开始查
    int skipIndex = (findDTO.getPage() - 1) * findDTO.getPageSize();

    // 排序顺序
    Bson sort = findDTO.getAsc() ? Sorts.ascending(findDTO.getSortField()) : Sorts.descending(findDTO.getSortField());

    FindIterable<Document> mapItemList = mongoTemplate.getCollection("mapItem")
        .find(query)
        .sort(Sorts.orderBy(sort))
        .skip(skipIndex)
        .limit(findDTO.getPageSize());


    for (Document document : mapItemList) {
        System.out.println(document);
    }


    // try {
    //     while (cursor.hasNext()) {
    //         //查询出的结果转换成jsonObject
    //         System.out.println(cursor.next());
    //         JSONObject jsonObject = JSONObject.parseObject( cursor.next().toJson());
    //         System.out.println();
    //     }
    // } catch (Exception e) {
    //     e.printStackTrace();
    // } finally {
    //     cursor.close();
    // }

    return null;
}

空间查询——查Polygon元素

$geoWithin

官网:The specified shape can be either a GeoJSON Polygon

查询字段的类型
1
2
// @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
GeoJsonPolygon box; // 图像box范围
构造框选参数

(不能用box了,查不出来,必须用Polygon/GeoJsonPolygon)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
double left_bottom_lon = 24.482901;
double left_bottom_lat = -38.771001;
double right_upper_lon = 150.443456;
double right_upper_lat = 58.526297;

GeoJsonPolygon polygon = new GeoJsonPolygon(
    new Point(left_bottom_lon, left_bottom_lat),
    new Point(left_bottom_lon, right_upper_lat),
    new Point(right_upper_lon, right_upper_lat),
    new Point(right_upper_lon, left_bottom_lat),
    new Point(left_bottom_lon, left_bottom_lat)
);

选择框属性的类型必须是GeonPolygon

使用mongoRepository方式查询
1
2
Page<T> findByNameContainsIgnoreCaseAndPolygonWithinAndClassifications(
    String name,  GeoJsonPolygon polygon, List<String> classifications, Pageable pageable);
使用mongoTemplate方式查询
1
2
3
4
5
6
7
8
public List<MapItem> findInPolygon(Polygon polygon, List<String> classifications){
    Query query = new Query();
    // query.addCriteria(Criteria.where("box").within(polygon));
    query.addCriteria(Criteria.where("polygon").intersects(polygon));
    query.addCriteria(Criteria.where("classifications").in(classifications));

    return mongoTemplate.find(query, MapItem.class, "basicScaleMap");
}

注意:mongoRepository只支持within的查询,要使用intersects的查询必须使用mongoTemplate,同时使用intersects查询时查询框必须是GeoJsonPolygon类

遇到的问题

空间查询点要素,范围框选太大查不出来 (已解决)
 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
{
    "findDTO": {
        "asc": false,
        "page": 1,
        "pageSize": 4,
        "sortField": "createTime"
    },
    "pointList": [
        [
           -38,
            -42
        ],
        [
            162,
            -42
        ],
        [
            162,
            63
        ],
        [
            -38,
            63
        ],
        [
            -38,
            -42
        ]
    ]
}

使用上面的测试数据, 运行下面的代码返回的结果为空

 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
public JsonResult findByPolygon(SpatialDTO spatialDTO) {

        List<List<Double>> pointList = spatialDTO.getPointList();
        FindDTO findDTO = spatialDTO.getFindDTO();

        List<Point> list = new ArrayList<>();

        for (int i = 0; i < 5; i++) {
            List<Double> p = pointList.get(i);
            Point point = new Point(p.get(0),p.get(1));
            list.add(point);
        }


        GeoJsonPolygon geoJsonPolygon = new GeoJsonPolygon(list);

        Pageable pageable = genericService.getPageable(findDTO);

        Page<MapItem> items = mapItemDao.findAllByCenterWithin(geoJsonPolygon, pageable);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("count", items.getTotalElements());
        jsonObject.put("content", items.getContent());

        return ResultUtils.success(jsonObject);
    }

下面这个查得出来

 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
{
    "findDTO": {
        "asc": false,
        "page": 1,
        "pageSize": 4,
        "sortField": "createTime"
    },
    "pointList": [
        [
           -38,
            -42
        ],
        [
            137,
            -42
        ],
        [
            137,
            63
        ],
        [
            -38,
            63
        ],
        [
            -38,
            -42
        ]
    ]
}

做了下测试,发现Within对Polygon类的支持比较好,虽然GeoJsonPolygon继承Polygon类,但是当范围很大的时候就是查不出来

image-20211214154514717

还有一点要注意的是,在mongodb中空间查询使用的是$geoWithin , 但是为什么在mongorepository中用的是within呢?

mongoRepository使用within == 在mongodb中使用$geoWithin

参考官方文档

空间查询polygon,范围框选查不到所有包含在内的数据
 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
{
    "findDTO": {
        "asc": false,
        "page": 1,
        "pageSize": 4,
        "sortField": "createTime"
    },
    "pointList": [
        [
           -38,
            -42
        ],
        [
            145,
            -42
        ],
        [
            145,
            63
        ],
        [
            -38,
            63
        ],
        [
            -38,
            -42
        ]
    ]
}

经过多次对 $geoIntersects 的实验,发现几个现象:

1.经度的查询跨度必须小于180°

2.如果经度查询跨度大于180°,只会显示出数据库中polygon字段与查询范围相交的记录

3.但是做了个测试经度范围在-170~140竟然所有的都能查得出来,而且查出来的记录并不在范围内

image-20220401155948418

image-20220401160310811

查看官网的这句哈可以发现,当超过半个球的时候,他会查询一个互补几何,我猜测可能就是这块橙色区域,所以可以把所有的都查出来

image-20220401160509998

再做一个测试,框选一个范围,该范围经度跨度大于180°,且包含数据库中的记录下图红框。会发现一个都没查出来,这就可以证实了当经度跨度大于180的时候,他确实找的是红色的这块区域,也即

(-180°, 180°) - (leftLon, rightLon) 集合区域

image-20220401160858516

4.经度最大不能超过180°,最小不能少于180°,不然会报错 longitude/latitude is out of bounds

依据上面的现象完成了针对不同情况的空间查询

Service

 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
public JsonResult findByPolygon(SpatialDTO spatialDTO) {

    List<List<Double>> pointList = spatialDTO.getPointList();
    SpecificFindDTO findDTO = spatialDTO.getFindDTO();

    Pageable pageable = genericService.getPageable(findDTO);

    Double leftLon = pointList.get(0).get(0);
    Double rightLon = pointList.get(2).get(0);
    Double bottomLat = pointList.get(0).get(1);
    Double upperLat = pointList.get(2).get(1);

    String curQueryField = findDTO.getCurQueryField();
    if (curQueryField == null || curQueryField.equals("")){
        curQueryField = "name";
    }

    List<MapClassification> mapClassifications = buildClassifications(findDTO.getMapCLSId());

    List<MapItem> mapItemList = null;
    List<GeoJsonPolygon> queryPolygon = getQueryPolygon(leftLon, rightLon, bottomLat, upperLat);

    if (queryPolygon.size() == 1){
        mapItemList = mapItemDao.findBySearchTextAndPolygonAndPageable(
            curQueryField, findDTO.getSearchText(), queryPolygon.get(0), mapClassifications, pageable);
    } else {
        mapItemList = mapItemDao.findBySearchTextAndPolygonAndPageable(
            curQueryField, findDTO.getSearchText(), new GeoJsonMultiPolygon(queryPolygon), mapClassifications, pageable);
    }

    return ResultUtils.success(mapItemList);
}

//得到查询的多边形范围
private List<GeoJsonPolygon> getQueryPolygon(double leftLon, double rightLon,double bottomLat,double upperLat){

    List<GeoJsonPolygon> polygons = new ArrayList<>();

    // 1.先把框选范围移到左边经度大于-180的情况
    while (leftLon < -180){
        leftLon += 360;
        rightLon += 360;
    }


    // 2.如果右边经度此时小于180的话
    // 接着判断经度跨度是否小于180,
    // 如果小于的话直接通过GeoJsonPolygon查找
    // 如果大于的话就把box从中间切开,通过GeoJsonMultiPolygon进行查找
    if (rightLon < 180){
        polygons.addAll(getQueryPolygon_standard(leftLon,rightLon,bottomLat,upperLat));
    }

    // 3.如果右边经度此时大于等于180的话
    // 就要把 >=180 的范围从180°经线切开,形成两个box进行查找
    // 分别是
    // (leftLon, 179.9)
    // (-179.9, rightLon-360)
    // 由于这两个box肯定是符合第二个情况的,所以接下来这两个box分别进行第二步的讨论就行
    else {
        polygons.addAll(getQueryPolygon_standard(leftLon,179.9,bottomLat,upperLat));
        polygons.addAll(getQueryPolygon_standard(-179.9,rightLon-360,bottomLat,upperLat));
    }


    return polygons;

}


//上面的第二个步骤是要多次调用的,单独抽出来
private List<GeoJsonPolygon> getQueryPolygon_standard(Double leftLon, Double rightLon,Double bottomLat,Double upperLat){

    List<GeoJsonPolygon> polygons = new ArrayList<>();

    if (rightLon - leftLon < 180){
        polygons.add(new GeoJsonPolygon(
            new Point(leftLon,bottomLat),new Point(leftLon,upperLat),
            new Point(rightLon,upperLat),new Point(rightLon,bottomLat),
            new Point(leftLon,bottomLat)));
    }else {
        double middleLon = (rightLon + leftLon) / 2;
        polygons.add(new GeoJsonPolygon(new Point(leftLon,bottomLat),new Point(leftLon,upperLat),
                                        new Point(middleLon,upperLat),new Point(middleLon,bottomLat),new Point(leftLon,bottomLat)));
        polygons.add(new GeoJsonPolygon(
            new Point(middleLon,bottomLat),new Point(middleLon,upperLat),
            new Point(rightLon,upperLat),new Point(rightLon,bottomLat),new Point(middleLon,bottomLat)));
    }

    return polygons;
}

Dao

 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
public List<MapItem> findBySearchTextAndPolygonAndPageable(
    String curQueryField, String searchText,
    GeoJsonPolygon polygon, List<MapClassification> clsIdList, Pageable pageable) {

    Query query = new Query();
    // query.addCriteria(Criteria.where("box").within(box));
    query.addCriteria(Criteria.where(curQueryField).regex(searchText));
    query.addCriteria(Criteria.where("polygon").intersects(polygon));
    if (clsIdList.size() != 0)
        query.addCriteria(Criteria.where("mapCLS").in(clsIdList));

    return mongoTemplate.find(query.with(pageable), MapItem.class);
}

public List<MapItem> findBySearchTextAndPolygonAndPageable(
    String curQueryField, String searchText,
    GeoJsonMultiPolygon polygon, List<MapClassification> clsIdList, Pageable pageable) {
    Query query = new Query();
    // query.addCriteria(Criteria.where("box").within(box));
    query.addCriteria(Criteria.where(curQueryField).regex(searchText));
    query.addCriteria(Criteria.where("polygon").intersects(polygon));
    if (clsIdList.size() != 0)
        query.addCriteria(Criteria.where("mapCLS").in(clsIdList));
    List<MapItem> mapItemList = mongoTemplate.find(query.with(pageable), MapItem.class);
    return mapItemList;
}

MongoDB语法

1
2
3
4
5
6
7
8
9
db.basicScaleMap.find({box:{
    $geoWithin: {
        $geometry: { 
            type: "Polygon", 
            coordinates: [[[24.482901, -38.771001],[24.482901, 58.526297],[150.443456, 58.526297],[150.443456, -38.771001],[24.482901, -38.771001]]] }
    }}})
   .projection({})
   .sort({_id:-1})
   .limit(100)
img

创建空间索引

创建索引

db.collection.createIndex(keys, options)

语法中 Key 值为你要创建的索引字段,1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可

db.col.createIndex({"title":1})

创建空间索引

https://docs.mongodb.com/manual/core/2dsphere/

img

db.mapItem.createIndex( { center : "2dsphere" } )