用户任健 發表於 2023-8-26 19:58:00

使用 Mongodb 进行地理空间查询

<p>目前越来越多的项目和产品,需要具有空间查询的需求,如外卖送餐时骑手的定位,地图上搜索以自己为中心点附近的餐厅等等,所以当前基本上所有的关系型数据库以及 nosql 数据库都具有空间查询的函数功能。但是总体而言 nosql 数据库的空间查询性能更高,这里不深入探讨具体的原因,有兴趣可以自行查询资料或动手试验对比。本篇博客主要从代码层面介绍如何通过 SpringData 操作 mongodb 实现对空间数据的查询操作。</p>
<p>对于空间查询,一般分为平面几何空间查询,以及球面地理空间查询,绝大多数情况下,我们都使用球面地理空间,这就要求每个点必须是有效的经纬度点。对于 mongodb 来说,需要针对经纬度数据建立 2DSphere 索引,然后才能进行球面地理空间查询。本篇博客主要介绍最常用的点是否在面内的判断,以及在查询结果中显示与给定点的距离等等,在本篇博客的最后会提供源代码下载。</p>
<p>Mongodb 的中文官网地址:https://www.mongodb.com/zh-cn</p>
<p>Spring Data Mongodb 的官网地址:https://spring.io/projects/spring-data-mongodb</p>
<br>
<h3 id="一搭建工程">一、搭建工程</h3>
<p>我的虚拟机 ip 地址是:192.168.136.128,仍然使用 docker-compose 部署 mongodb,初始化一个 root 角色的账号 jobs ,密码是 123456。新建一个 SpringBoot 工程,结构如下所示:</p>
<p><img src="https://img2023.cnblogs.com/blog/2502715/202308/2502715-20230826195704400-2002959343.jpg"></p>
<p>Company 是公司类,针对 mongodb 数据库中要操作的表 tb_company 而创建。</p>
<p>MongoGeoTest 类中编写了一些测试方法,用来对 mongodb 中 tb_company 表进行空间查询。</p>
<p>该工程的 pom 文件内容如下:(最主要的是引入 spring-boot-starter-data-mongodb 这个依赖)</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;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"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

    &lt;groupId&gt;com.jobs&lt;/groupId&gt;
    &lt;artifactId&gt;springboot_mongo_geo&lt;/artifactId&gt;
    &lt;version&gt;1.0&lt;/version&gt;

    &lt;properties&gt;
      &lt;maven.compiler.source&gt;8&lt;/maven.compiler.source&gt;
      &lt;maven.compiler.target&gt;8&lt;/maven.compiler.target&gt;
    &lt;/properties&gt;

    &lt;parent&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
      &lt;version&gt;2.3.9.RELEASE&lt;/version&gt;
    &lt;/parent&gt;

    &lt;dependencies&gt;
      &lt;!--引入最基本的 springboot 依赖--&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;
      &lt;/dependency&gt;
      &lt;!--引入 springdata mongodb 的依赖--&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-data-mongodb&lt;/artifactId&gt;
      &lt;/dependency&gt;
      &lt;!--引入 test 依赖--&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
            &lt;scope&gt;test&lt;/scope&gt;
      &lt;/dependency&gt;
      &lt;!--引入该依赖,可以打印日志,以及省去实例类的 get 和 set 方法--&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
            &lt;artifactId&gt;lombok&lt;/artifactId&gt;
      &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/project&gt;
</code></pre>
<p>项目工程中的 application.yml 文件如下,主要配置了 mongodb 的连接字符串</p>
<pre><code class="language-yaml">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
</code></pre>
<br>
<h3 id="二代码细节">二、代码细节</h3>
<p>实体类 Company 的具体细节如下:</p>
<pre><code class="language-java">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;
}
</code></pre>
<p>需要注意的是:必须要有 @GeoSpatialIndexed 的索引注解,这样 mongodb 才知道哪个字段是空间字段。</p>
<p>如上所示针对 location 数组创建了地理空间索引(索引的类型是 2dSphere )</p>
<p>在 MongoGeoTest 类中编写了空间查询的测试代码,具体细节如下:</p>
<pre><code class="language-java">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&lt;Company&gt; companyList = new ArrayList&lt;&gt;();
      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&lt;Company&gt; 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&lt;Company&gt; results = mongoTemplate.geoNear(query, Company.class);
      //打印查询结果,按照距离给定点的距离由近到远排列
      for (GeoResult&lt;Company&gt; result : results) {
            Company company = result.getContent();
            double value = result.getDistance().getValue();
            System.out.println(company.getName() + " 距离给定点:" + (int) (value * 1000) + " 米");
      }
    }

    //查询自定义多边形区域内的公司
    @Test
    public void testRectangle() {
      //构建一个三角形(需要注意:多边形首尾两个点的坐标必须相同)
      List&lt;Point&gt; points = new ArrayList&lt;&gt;();
      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&lt;Company&gt; list = mongoTemplate.find(query, Company.class);
      list.forEach(System.out::println);
    }
}
</code></pre>
<br>
<p>当然 mongodb 也支持点、线、面,以及之间的交互查询判断,这里就不再介绍了,一般很少使用。</p>
<p>有兴趣的话,可以查询其它网上的相关资料,以及 Spring Data Mongodb 官网的 api 文档介绍。</p>
<p>本篇博客的源代码下载地址:https://files.cnblogs.com/files/blogs/699532/springboot_mongo_geo.zip</p>
<br><br><br>
来源:https://www.cnblogs.com/studyjobs/p/17659377.html
頁: [1]
查看完整版本: 使用 Mongodb 进行地理空间查询