Spring Data MongoDB 教程

地理空间查询

MongoDB 通过使用 $near$withingeoWithin$nearSphere 等操作符支持地理空间查询,地理空间查询的特定方法在 Criteria 类中可用。还有一些形状类(Box、Circle 和 Point)可以与地理空间相关的 Criteria 方法结合使用。

注意:在 MongoDB 事务中使用 地理空间(GeoSpatial)查询时需要注意事务中的一些特殊行为。

示例

该示例将演示如何执行地理空间(GeoSpatial)查询。

(1)城市实体 City,代码如下:

package com.hxstrive.springdata.mongodb.entity;

import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import org.springframework.data.annotation.Id;

@Data
@Builder
@ToString
public class City {
   @Id
   private String id;
   /** 城市名称 */
   private String name;
   /** 城市位置 */
   private double[] location;
}

(2)客户端,通过单元测试演示各种基本的地理空间查询,代码如下:

package com.hxstrive.springdata.mongodb.query;

import com.hxstrive.springdata.mongodb.entity.City;
import org.junit.jupiter.api.BeforeEach;
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.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeospatialIndex;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import java.util.List;

@SpringBootTest
public class GeoQueryTest {

   @Autowired
   private MongoTemplate mongoTemplate;

   @BeforeEach
   public void init() {
       // 删除集合,清空数据
       mongoTemplate.dropCollection(City.class);

       // 准备数据
       mongoTemplate.insert(City.builder().id("1").name("北京").location(new double[]{116.408213, 39.914687}).build());
       mongoTemplate.insert(City.builder().id("2").name("上海").location(new double[]{121.473641,31.233212}).build());
       mongoTemplate.insert(City.builder().id("3").name("深圳").location(new double[]{114.069726,22.546053}).build());
       mongoTemplate.insert(City.builder().id("4").name("武汉").location(new double[]{114.262718,30.576332}).build());
       mongoTemplate.insert(City.builder().id("5").name("成都").location(new double[]{104.075045,30.667253}).build());
       mongoTemplate.insert(City.builder().id("6").name("金牛区").location(new double[]{104.054923,30.697565}).build());
       mongoTemplate.insert(City.builder().id("7").name("成华区").location(new double[]{104.107815,30.666259}).build());
       mongoTemplate.insert(City.builder().id("8").name("温江区").location(new double[]{103.856577,30.690112}).build());
   }

   /**
    * 要查找圆内的位置
    */
   @Test
   public void demo1() {
       // 设置圆的中心点为:104.075045,30.667253
       // 半径为:0.1,单位 Metrics.NEUTRAL
       // public Circle(double centerX, double centerY, double radius) {
       //     this(new Point(centerX, centerY), new Distance(radius));
       // }
       // public Distance(double value) {
       //     this(value, Metrics.NEUTRAL);
       // }
       Circle circle = new Circle(104.075045,30.667253, 0.1);
       List<City> citys = mongoTemplate.find(
               new Query(Criteria.where("location").within(circle)), City.class);
       for(City city : citys) {
           System.out.println(city);
       }
   }

   /**
    * 使用球面坐标查找圆内的地点
    */
   @Test
   public void demo2() {
       // Metrics.KILOMETERS(6378.137, "km") 表示公里
       // Metrics.MILES(3963.191, "mi") 表示英里
       // Metrics.NEUTRAL(1, "") 源码没有注释,不知什么含义
       // 附近10公里
       Circle circle = new Circle(new Point(104.075045,30.667253),
               new Distance(10, Metrics.KILOMETERS));
       List<City> citys = mongoTemplate.find(new Query(Criteria.where("location").withinSphere(circle)), City.class);
       for(City city : citys) {
           System.out.println(city);
       }
   }

   /**
    * 在 Box(方框)中查找地点
    */
   @Test
   public void demo3() {
       // 指定 Box(方框)左下角和右上角的坐标
       Box box = new Box(new Point(104.075045,30.667253), new Point(104.196783,30.619808));
       List<City> citys = mongoTemplate.find(new Query(Criteria.where("location").within(box)), City.class);
       for(City city : citys) {
           System.out.println(city);
       }
   }

   /**
    * 要查找 Point 附近的地点
    */
   @Test
   public void demo5() {
       // 为 location 字段创建 GEO_2D 类型的索引,否则执行 near() 操作报如下错误:
       // unable to find index for $geoNear query' on server localhost:27017
       mongoTemplate.indexOps(City.class).ensureIndex(
               new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2D));

       Point point = new Point(104.075045,30.667253);
       List<City> citys = mongoTemplate.find(
               new Query(Criteria.where("location").near(point).maxDistance(0.1)), City.class);
       for(City city : citys) {
           System.out.println(city);
       }
   }

   @Test
   public void demo6() {
       // 为 location 字段创建 GEO_2D 类型的索引,否则执行 near() 操作报如下错误:
       // unable to find index for $geoNear query' on server localhost:27017
       mongoTemplate.indexOps(City.class).ensureIndex(
               new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2D));

       Point point = new Point(104.075045,30.667253);
       List<City> citys =
               mongoTemplate.find(new Query(Criteria.where("location").near(point)
                       .minDistance(0.01)
                       .maxDistance(1)), City.class);
       for(City city : citys) {
           System.out.println(city);
       }
   }

   /**
    * 使用球坐标查找点附近的地点
    */
   @Test
   public void demo7() {
       // 为 location 字段创建 GEO_2D 类型的索引,否则执行 near() 操作报如下错误:
       // unable to find index for $geoNear query' on server localhost:27017
       mongoTemplate.indexOps(City.class).ensureIndex(
               new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2D));

       Point point = new Point(104.075045,30.667253);
       List<City> citys = mongoTemplate.find(new Query(
               Criteria.where("location").nearSphere(point).maxDistance(0.1)), City.class);
       for(City city : citys) {
           System.out.println(city);
       }
   }

}

Geo-near查询

警告

在 2.2 中有所改变!

MongoDB 4.2 删除了对 geoNear 命令的支持,该命令之前被用于运行 NearQuery。

Spring Data MongoDB 2.2 的 MongoOperations#geoNear 方法使用 $geoNear 聚合而不是 geoNear 命令来运行 NearQuery。

以前在包装类型中返回的计算距离(使用 geoNear 命令时的 dis)现在被嵌入到结果文档中。如果给定的域类型已经包含了一个具有该名称的属性,那么计算出的距离将被命名为带有潜在随机后缀的 calculated-distance。

目标类型可以包含一个以返回的距离命名的属性,以直接读回域类型中。如下代码所示:

GeoResults<VenueWithDisField> = template.query(Venue.class) (1)
    .as(VenueWithDisField.class)                            (2)
    .near(NearQuery.near(new GeoJsonPoint(-73.99, 40.73), KILOMETERS))
    .all();

(1)用于识别目标集合和潜在查询映射的域类型。

(2)目标类型包含一个 Number 类型的 dis 字段。

MongoDB 支持查询数据库中的地理位置,并同时计算与给定原点的距离。通过地理距离查询,你可以表达诸如 "找到周围10英里内的所有餐馆" 这样的查询。为了让你这样做,MongoOperations 提供了 geoNear(...) 方法,该方法以 NearQuery 为参数(以及已经熟悉的实体类型和集合),如下面的例子中所示:

// 使用经纬度设置原点
Point location = new Point(-73.99171, 40.738868);
// Metrics.MILES 表示英里
NearQuery query = NearQuery.near(location).maxDistance(new Distance(10, Metrics.MILES));
// 进行 geo-near 查询
GeoResults<Restaurant> = operations.geoNear(query, Restaurant.class);

我们使用 NearQuery 构建器 API 来构造一个查询,以返回 “给定点周围10英里以内的所有餐厅” 实例。这里使用的 Metrics 枚举实际上实现了一个接口(Metric),这样其他的度量也可以插入到一个距离中。一个度量标准由一个乘数支持,用于将给定度量标准的距离值转换成本地距离。使用一个内置的度量(英里和公里)自动触发在查询中设置的球形标志。如果你想避免这种情况,可以将普通的 double 值传入 maxDistance(...) 方法。

Geo-near 操作返回一个封装了 GeoResult 实例的 GeoResults 包装对象。包装对象 GeoResults 可以访问所有结果的平均距离。一个 GeoResult 对象携带着所发现的实体以及它与原点的距离。

说说我的看法
全部评论(
没有评论
关于
本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明,如果原文没有版权声明,请来信告知:hxstrive@outlook.com
公众号