MongoDB 通过使用 $near、$within、geoWithin 和 $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); } } }
警告
在 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 对象携带着所发现的实体以及它与原点的距离。