使用 Mongodb 进行地理空间查询

目前越来越多的项目和产品,需要具有空间查询的需求,如外卖送餐时骑手的定位,地图上搜索以自己为中心点附近的餐厅等等,所以当前基本上所有的关系型数据库以及 nosql 数据库都具有空间查询的函数功能。但是总体而言 nosql 数据库的空间查询性能更高,这里不深入探讨具体的原因,有兴趣可以自行查询资料或动手试验对比。本篇博客主要从代码层面介绍如何通过 SpringData 操作 mongodb 实现对空间数据的查询操作。

对于空间查询,一般分为平面几何空间查询,以及球面地理空间查询,绝大多数情况下,我们都使用球面地理空间,这就要求每个点必须是有效的经纬度点。对于 mongodb 来说,需要针对经纬度数据建立 2DSphere 索引,然后才能进行球面地理空间查询。本篇博客主要介绍最常用的点是否在面内的判断,以及在查询结果中显示与给定点的距离等等,在本篇博客的最后会提供源代码下载。

Mongodb 的中文官网地址:https://www.mongodb.com/zh-cn

Spring Data Mongodb 的官网地址:https://spring.io/projects/spring-data-mongodb


一、搭建工程

我的虚拟机 ip 地址是:192.168.136.128,仍然使用 docker-compose 部署 mongodb,初始化一个 root 角色的账号 jobs ,密码是 123456。新建一个 SpringBoot 工程,结构如下所示:

image

Company 是公司类,针对 mongodb 数据库中要操作的表 tb_company 而创建。

MongoGeoTest 类中编写了一些测试方法,用来对 mongodb 中 tb_company 表进行空间查询。

该工程的 pom 文件内容如下:(最主要的是引入 spring-boot-starter-data-mongodb 这个依赖)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jobs</groupId>
    <artifactId>springboot_mongo_geo</artifactId>
    <version>1.0</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
    </parent>

    <dependencies>
        <!--引入最基本的 springboot 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--引入 springdata mongodb 的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <!--引入 test 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--引入该依赖,可以打印日志,以及省去实例类的 get 和 set 方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

项目工程中的 application.yml 文件如下,主要配置了 mongodb 的连接字符串

spring:
  data:
    mongodb:
      # 连接字符串格式
      # mongodb://用户名:密码@Ip地址:端口/数据库名
      # 如果使用的是 root 角色的用户登录,则必须在后面加上 authSource=admin 参数
      # 之前在 admin 库中创建了一个 root 角色的账号 jobs
      # 在实际项目中,强烈建议,针对每个数据库创建一个 readwrite 角色的用户
      uri: mongodb://jobs:123456@192.168.136.128:27017/mytest?authSource=admin
      # 允许在实体类上,索引注解生效,可以在 mongodb 表中建立相应的索引
      auto-index-creation: true

二、代码细节

实体类 Company 的具体细节如下:

package com.jobs.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoId;

//使用该注解,类中可以省略 get set 等方法的编写
@Data
//使用该注解,标明要操作的 mongodb 的文档(相当于数据库的表)
@Document("tb_company")
//使用该注解,可以使用对象实例化赋值采用链式编写
@Accessors(chain = true)
public class Company {

    //使用该注解,标明 mongodb 文档的主键 id
    @MongoId
    private String id;

    //如果 mongodb 文档的字段名与该实体类的字段名不一致
    //使用该注解,标明 mongodb 文档中实际的字段名
    @Field("cname")
    private String name; //企业名称

    //该索引表示空间索引(必须要使用该索引,否则空间查询可能会报错)
    //GEO_2DSPHERE 表示类似球面数据存储索引
    //数组中,0 索引存储经度,1 索引存储纬度
    @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
    private double[] location;
}

需要注意的是:必须要有 @GeoSpatialIndexed 的索引注解,这样 mongodb 才知道哪个字段是空间字段。

如上所示针对 location 数组创建了地理空间索引(索引的类型是 2dSphere )

在 MongoGeoTest 类中编写了空间查询的测试代码,具体细节如下:

package com.jobs.test;

import com.jobs.pojo.Company;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;

import java.util.ArrayList;
import java.util.List;

@SpringBootTest
public class MongoGeoTest {

    //注入 spring-data-mongodb 自带的 MongoTemplate 对象
    @Autowired
    private MongoTemplate mongoTemplate;

    //添加空间测试的样例数据
    @Test
    public void addSampleTestData() {

        List<Company> companyList = new ArrayList<>();
        companyList.add(new Company().setName("任肥肥红烧肉")
                .setLocation(new double[]{116.409488, 39.917015}));
        companyList.add(new Company().setName("侯胖胖烤全鱼")
                .setLocation(new double[]{116.39818, 39.918844}));
        companyList.add(new Company().setName("王棒棒火锅店")
                .setLocation(new double[]{116.411615, 39.921669}));
        companyList.add(new Company().setName("李墩墩咖啡馆")
                .setLocation(new double[]{116.409229, 39.920868}));
        companyList.add(new Company().setName("范呆呆羊肉汤")
                .setLocation(new double[]{116.3949, 39.97528}));
        companyList.add(new Company().setName("蔺赞赞刀削面")
                .setLocation(new double[]{116.398053, 39.920301}));
        companyList.add(new Company().setName("杨壮壮家常菜")
                .setLocation(new double[]{116.412929, 39.917812}));
        companyList.add(new Company().setName("乔豆豆大排档")
                .setLocation(new double[]{116.409182, 39.921745}));

        //批量添加 8 条样例数据
        mongoTemplate.insert(companyList, Company.class);
    }

    //给定一个中心点,查询 800 米以内的公司
    @Test
    public void testCircle() {
        //指定中心点
        GeoJsonPoint point = new GeoJsonPoint(116.404, 39.915);
        //KILOMETERS 表示千米
        Distance distance = new Distance(0.8, Metrics.KILOMETERS);
        //构建一个圆形范围
        Circle circle = new Circle(point, distance);
        //构造查询条件
        Query query = Query.query(Criteria.where("location").withinSphere(circle));
        //5、查询
        List<Company> list = mongoTemplate.find(query, Company.class);
        list.forEach(System.out::println);
    }

    //查询距离给定的中心点 800 米以内的公司,并按照距离,由近到远进行排列。
    @Test
    public void testNearest() {
        //指定中心点
        GeoJsonPoint point = new GeoJsonPoint(116.404, 39.915);
        //指定查询条件
        NearQuery query = NearQuery.near(point).maxDistance(new Distance(0.8, Metrics.KILOMETERS));
        //由于 Company 的字段 location 设置了空间索引,因此针对该字段与给定点进行距离计算并筛选。
        GeoResults<Company> results = mongoTemplate.geoNear(query, Company.class);
        //打印查询结果,按照距离给定点的距离由近到远排列
        for (GeoResult<Company> result : results) {
            Company company = result.getContent();
            double value = result.getDistance().getValue();
            System.out.println(company.getName() + " 距离给定点:" + (int) (value * 1000) + " 米");
        }
    }

    //查询自定义多边形区域内的公司
    @Test
    public void testRectangle() {
        //构建一个三角形(需要注意:多边形首尾两个点的坐标必须相同)
        List<Point> points = new ArrayList<>();
        points.add(new Point(116.361, 39.924));
        points.add(new Point(116.415, 39.933));
        points.add(new Point(116.393, 39.891));
        points.add(new Point(116.361, 39.924));

        GeoJsonPolygon polygon = new GeoJsonPolygon(points);
        Query query = Query.query(Criteria.where("location").within(polygon));
        List<Company> list = mongoTemplate.find(query, Company.class);
        list.forEach(System.out::println);
    }
}

当然 mongodb 也支持点、线、面,以及之间的交互查询判断,这里就不再介绍了,一般很少使用。

有兴趣的话,可以查询其它网上的相关资料,以及 Spring Data Mongodb 官网的 api 文档介绍。

本篇博客的源代码下载地址:https://files.cnblogs.com/files/blogs/699532/springboot_mongo_geo.zip


posted @ 2023-08-26 19:58  乔京飞  阅读(11791)  评论(0编辑  收藏  举报