使用Spring Data MongoDB进行地理位置相关查询的步骤和示例

 更新时间:2025年05月27日 08:51:16   作者:冰糖心书房  
SpringData MongoDB是SpringData技术封装了mongodb-driver技术之后的产物,它可以用更加简单的方式操作MongoDB,本文给大家介绍了如何使用Spring Data MongoDB进行地理位置相关查询的步骤和示例,需要的朋友可以参考下

以下是如何使用 Spring Data MongoDB 进行地理位置相关查询的步骤和示例:

核心概念:

  1. GeoJSON 对象: MongoDB 推荐使用 GeoJSON 格式来存储地理位置数据。Spring Data MongoDB 提供了相应的 GeoJSON 类型,如 GeoJsonPointGeoJsonPolygonGeoJsonLineString 等。
    • GeoJsonPoint: 表示一个点,例如 [longitude, latitude]
  2. 地理空间索引 (Geospatial Index): 为了高效地执行地理位置查询,必须在存储位置数据的字段上创建地理空间索引。
    • 2dsphere: 支持球面几何计算,适用于地球表面的经纬度数据(推荐)。
    • 2d: 支持平面几何计算,适用于二维平面上的点。
  3. 查询操作符: MongoDB 提供了多种地理位置查询操作符:
    • $near / $nearSphere: 查找靠近某个点的文档,并按距离排序。
    • $geoWithin: 查找几何形状(如多边形、圆形)内的文档。
    • $geoIntersects: 查找与指定 GeoJSON 对象相交的文档。
    • $centerSphere (与 $geoWithin 结合使用): 定义一个球心和半径的圆形区域进行查询。

步骤详解:

步骤 1: 添加依赖

确保你的 pom.xml (Maven) 或 build.gradle (Gradle) 文件中包含 Spring Data MongoDB 的依赖:

<!-- pom.xml (Maven) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

步骤 2: 定义实体 (Entity)

在你的实体类中,使用 org.springframework.data.mongodb.core.geo.GeoJsonPoint (或其他 GeoJSON 类型) 来存储位置信息。

import org.springframework.data.annotation.Id;
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;

@Document(collection = "locations")
public class LocationEntity {

    @Id
    private String id;
    private String name;

    // 存储经纬度信息,并创建 2dsphere 索引
    @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
    private GeoJsonPoint location; // [longitude, latitude]

    public LocationEntity() {}

    public LocationEntity(String name, GeoJsonPoint location) {
        this.name = name;
        this.location = location;
    }

    // Getters and Setters
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public GeoJsonPoint getLocation() {
        return location;
    }

    public void setLocation(GeoJsonPoint location) {
        this.location = location;
    }

    @Override
    public String toString() {
        return "LocationEntity{" +
               "id='" + id + '\'' +
               ", name='" + name + '\'' +
               ", location=" + (location != null ? location.getCoordinates() : null) +
               '}';
    }
}

注意:

  • @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) 注解会自动在 location 字段上创建 2dsphere 索引。这是进行地理位置查询的关键。
  • GeoJSON 点的坐标顺序是 [longitude, latitude] (经度在前,纬度在后)。

步骤 3: 创建 Repository 接口

Spring Data MongoDB 可以通过方法名派生查询,或者使用 @Query 注解自定义查询。

import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;

public interface LocationRepository extends MongoRepository<LocationEntity, String> {

    // 1. 查找靠近某个点的文档 (使用 $nearSphere)
    // Spring Data 会自动使用 $nearSphere 因为索引是 2dsphere
    // Point 来自 org.springframework.data.geo.Point (x=longitude, y=latitude)
    // Distance 来自 org.springframework.data.geo.Distance
    List<LocationEntity> findByLocationNear(Point point, Distance distance);

    // 也可以只按点查找,不限制距离 (结果按距离排序)
    List<LocationEntity> findByLocationNear(Point point);

    // 2. 查找在指定多边形内的文档 (使用 $geoWithin)
    // Polygon 来自 org.springframework.data.geo.Polygon
    List<LocationEntity> findByLocationWithin(Polygon polygon);

    // 3. 查找在指定圆形区域内的文档 (使用 $geoWithin 和 $centerSphere)
    // Circle 来自 org.springframework.data.geo.Circle
    // Spring Data 会将其转换为 $geoWithin 与 $centerSphere
    List<LocationEntity> findByLocationWithin(org.springframework.data.geo.Circle circle);

    // 4. 查找与指定 GeoJSON 几何图形相交的文档 (使用 $geoIntersects)
    // 需要使用 MongoTemplate 或 @Query 来实现更复杂的 GeoJSON 相交查询,
    // 因为派生查询对 $geoIntersects 的支持有限,尤其是对于复杂的 GeoJSON 输入。
    // 但简单的 Point 相交可以。
    // 对于更复杂的 GeoJSON (如 Polygon),通常使用 MongoTemplate 或 @Query
    // List<LocationEntity> findByLocationIntersects(GeoJson geometry); // 示例,可能需要自定义实现

}

使用的 Spring Data Geo 类型:

  • org.springframework.data.geo.Point: 用于查询参数,表示一个点 (x 对应经度, y 对应纬度)。
  • org.springframework.data.geo.Distance: 用于指定距离,可以包含单位 (如 Metrics.KILOMETERS)。
  • org.springframework.data.geo.Polygon: 用于查询参数,表示一个多边形。
  • org.springframework.data.geo.Circle: 用于查询参数,表示一个圆形。
  • org.springframework.data.geo.Box: 用于查询参数,表示一个矩形。

步骤 4: 使用 Repository 或 MongoTemplate 进行查询

import org.springframework.beans.factory.annotation.Autowired;
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.Query;
import org.springframework.stereotype.Service;

import jakarta.annotation.PostConstruct;
import java.util.Arrays;
import java.util.List;

@Service
public class LocationService {

    @Autowired
    private LocationRepository locationRepository;

    @Autowired
    private MongoTemplate mongoTemplate;

    @PostConstruct
    public void init() {
        locationRepository.deleteAll(); // 清理旧数据

        // 插入一些示例数据
        // 故宫 (116.403963, 39.915119)
        locationRepository.save(new LocationEntity("Forbidden City", new GeoJsonPoint(116.403963, 39.915119)));
        // 天安门广场 (116.3912757, 39.9037078)
        locationRepository.save(new LocationEntity("Tiananmen Square", new GeoJsonPoint(116.3912757, 39.9037078)));
        // 颐和园 (116.275136, 39.999077)
        locationRepository.save(new LocationEntity("Summer Palace", new GeoJsonPoint(116.275136, 39.999077)));
        // 东方明珠 (121.499718, 31.239703)
        locationRepository.save(new LocationEntity("Oriental Pearl Tower", new GeoJsonPoint(121.499718, 31.239703)));
    }

    public void performGeoQueries() {
        System.out.println("--- Performing Geo Queries ---");

        // 中心点: 北京市中心附近 (例如王府井 116.417427, 39.913904)
        Point centerPoint = new Point(116.417427, 39.913904); // longitude, latitude

        // 1. 查找王府井附近 5 公里内的地点
        Distance fiveKilometers = new Distance(5, Metrics.KILOMETERS);
        List<LocationEntity> nearWangfujing = locationRepository.findByLocationNear(centerPoint, fiveKilometers);
        System.out.println("\nLocations near Wangfujing (5km):");
        nearWangfujing.forEach(System.out::println); // 应该包含故宫和天安门

        // 2. 查找在指定多边形内的地点 (大致覆盖北京二环内)
        // 注意:多边形的点必须形成闭合环路,且第一个点和最后一个点相同
        Polygon beijingRing2 = new Polygon(
                new Point(116.30, 39.85), //西南
                new Point(116.50, 39.85), //东南
                new Point(116.50, 39.95), //东北
                new Point(116.30, 39.95), //西北
                new Point(116.30, 39.85)  //闭合
        );
        List<LocationEntity> withinBeijingRing2 = locationRepository.findByLocationWithin(beijingRing2);
        System.out.println("\nLocations within Beijing Ring 2 (approx):");
        withinBeijingRing2.forEach(System.out::println); // 应该包含故宫和天安门

        // 3. 查找在指定圆形区域内的地点 (以故宫为圆心,2公里为半径)
        Point forbiddenCityCoords = new Point(116.403963, 39.915119);
        Distance twoKilometers = new Distance(2, Metrics.KILOMETERS);
        // 对于2dsphere索引, Circle的距离单位会被正确处理 (例如转换为弧度)
        Circle aroundForbiddenCity = new Circle(forbiddenCityCoords, twoKilometers);
        List<LocationEntity> withinCircle = locationRepository.findByLocationWithin(aroundForbiddenCity);
        System.out.println("\nLocations within 2km of Forbidden City:");
        withinCircle.forEach(System.out::println); // 应该包含故宫和天安门

        // 4. 使用 MongoTemplate 进行 $geoIntersects 查询
        // 定义一个 GeoJsonPolygon (注意点顺序,逆时针为外部,顺时针为内部,但通常简单多边形即可)
        // 这里用和上面一样的多边形,但用 GeoJsonPolygon
        GeoJsonPolygon queryPolygon = new GeoJsonPolygon(
                new Point(116.30, 39.85),
                new Point(116.50, 39.85),
                new Point(116.50, 39.95),
                new Point(116.30, 39.95),
                new Point(116.30, 39.85)
        );
        Query intersectsQuery = new Query(Criteria.where("location").intersects(queryPolygon));
        List<LocationEntity> intersectingLocations = mongoTemplate.find(intersectsQuery, LocationEntity.class);
        System.out.println("\nLocations intersecting with query polygon (MongoTemplate):");
        intersectingLocations.forEach(System.out::println);


        // 5. 使用 MongoTemplate 进行 $nearSphere 查询,并指定最小和最大距离
        Query nearQueryWithMinMax = new Query(
            Criteria.where("location")
                    .nearSphere(centerPoint) // 使用 Spring Data Point
                    .minDistance(1000 / 6378137.0) // 最小距离1公里 (转换为弧度,MongoDB $nearSphere 需要弧度或米)
                                                   // 或者直接用米: .minDistance(1000) 如果MongoDB版本支持
                    .maxDistance(5000 / 6378137.0) // 最大距离5公里
                                                   // 或者直接用米: .maxDistance(5000)
        );
        // 如果MongoDB 4.0+ 且 Spring Data MongoDB 2.2+, 可以直接用米
        // Query nearQueryWithMinMaxMeters = new Query(
        // Criteria.where("location")
        // .nearSphere(centerPoint)
        // .minDistance(1000.0) // 1000 meters
        // .maxDistance(5000.0) // 5000 meters
        // );
        // List<LocationEntity> nearWithMinMax = mongoTemplate.find(nearQueryWithMinMaxMeters, LocationEntity.class);
        // System.out.println("\nLocations near Wangfujing (1km-5km, MongoTemplate):");
        // nearWithMinMax.forEach(System.out::println);

        // 对于 $nearSphere,Spring Data 的 Repository 方法中的 Distance 对象会自动处理单位转换。
        // 使用 MongoTemplate 时,对于 $minDistance / $maxDistance:
        // - 如果是 `2dsphere` 索引,MongoDB 期望距离单位是米。
        // - 如果是 `2d` 索引,MongoDB 期望距离单位是索引坐标系的单位。
        // Spring Data MongoDB 3.0+ 配合 MongoDB 4.0+,`nearSphere` 可以直接接受米为单位的 `minDistance`/`maxDistance`。
        // 如果使用较旧版本,可能需要将距离转换为弧度(如示例中除以地球半径)。
        // 简单的 findByLocationNear(Point, Distance) 通常是更方便的选择。
    }
}

运行示例 (在一个 Spring Boot 应用中):

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class MongoGeoApplication {

    public static void main(String[] args) {
        SpringApplication.run(MongoGeoApplication.class, args);
    }

    @Bean
    CommandLineRunner runner(LocationService locationService) {
        return args -> {
            locationService.performGeoQueries();
        };
    }
}

总结与要点:

  1. 实体定义: 使用 GeoJsonPoint (或其他 GeoJson* 类型) 存储位置,并用 @GeoSpatialIndexed 创建 2dsphere 索引。
  2. 坐标顺序: 始终记住 GeoJSON 使用 [longitude, latitude]。Spring Data 的 Point 对象构造函数 new Point(x, y) 中 x 是经度,y 是纬度。
  3. Repository 查询: Spring Data Repositories 为常见的地理位置查询(如 NearWithin)提供了便捷的方法名派生。
  4. MongoTemplate: 对于更复杂或自定义的地理位置查询(如 $geoIntersects 配合复杂 GeoJSON 对象,或需要更精细控制 $nearSphere 的 $minDistance/$maxDistance),可以使用 MongoTemplate
  5. 单位:
    • org.springframework.data.geo.Distance: 允许你指定单位 (如 Metrics.KILOMETERSMetrics.MILES)。Spring Data 会在与 MongoDB 交互时处理转换。
    • MongoDB 的 $nearSphere 和 $centerSphere (用于 2dsphere 索引) 默认使用作为距离单位。
    • 当使用 MongoTemplate 时,需要注意 minDistance/maxDistance 的单位,较新版本的 MongoDB (4.0+) 和 Spring Data MongoDB (2.2+/3.0+) 可以直接使用米。
  6. 性能: 地理空间索引对于查询性能至关重要。确保索引已正确创建。

以上就是使用Spring Data MongoDB进行地理位置相关查询的步骤和示例的详细内容,更多关于Spring Data MongoDB地理位置查询的资料请关注脚本之家其它相关文章!

相关文章

  • java使用webuploader实现跨域上传详解

    java使用webuploader实现跨域上传详解

    目前初步接触JAVA图片上传,用的webuploader。已经跟后台对接上,但是有个问题就是跨域请求,通过查找相关资料终于实现了,下面这篇文章主要给大家介绍了关于java使用webuploader实现跨域上传的相关资料,需要的朋友可以参考下。
    2017-07-07
  • Java实现冒泡排序算法及对其的简单优化示例

    Java实现冒泡排序算法及对其的简单优化示例

    这篇文章主要介绍了Java实现冒泡排序算法及对其的简单优化示例,冒泡排序的最差时间复杂度为O(n^2),最优时间复杂度为O(n),存在优化的余地,需要的朋友可以参考下
    2016-05-05
  • java搭建ftp/sftp进行数据传递的全过程

    java搭建ftp/sftp进行数据传递的全过程

    ftp是一种文件传输协议,让客户端和服务端能够互相传递文件,图片等数据,sftp也是一种文件传输协议,但是相比较而言要比ftp安全性更好些,但是也有缺点就是传输效率低
    2021-07-07
  • java变量内存中存储的使用方式

    java变量内存中存储的使用方式

    这篇文章主要介绍了java变量内存中存储的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-05-05
  • Java杂谈之代码重构的方法多长才算长

    Java杂谈之代码重构的方法多长才算长

    关于代码重构的理解:在不改变软件系统/模块所具备的功能特性的前提下,遵循/利用某种规则,使其内部结构趋于完善。其在软件生命周期中的价值体现主要在于可维护性和可扩展性
    2021-10-10
  • 详解java中float与double的区别

    详解java中float与double的区别

    这篇文章主要介绍了JAVA中float与double的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • 如何在Spring Boot启动时运行定制的代码

    如何在Spring Boot启动时运行定制的代码

    在本文中您将学习如何挂钩应用程序引导程序生命周期并在Spring Boot启动时执行代码。文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-12-12
  • Spring Boot线程池使用的一些实用心得

    Spring Boot线程池使用的一些实用心得

    理论上线程越多程序可能更快,但在实际使用中我们需要考虑到线程本身的创建以及销毁的资源消耗,以及保护操作系统本身的目的我们通常需要将线程限制在一定的范围之类,这篇文章主要给大家介绍了关于Spring Boot线程池使用的一些实用心得,需要的朋友可以参考下
    2021-09-09
  • Java中的Vector和Stack底层源码分析

    Java中的Vector和Stack底层源码分析

    这篇文章主要介绍了Java中的Vector和Stack底层源码分析,Stack继承了Vector,Vector底层还是一个List,也就是基于数组来实现的,所以ArrayList有的优点,比如获取元素的速度快,随机读,它都有,需要的朋友可以参考下
    2023-12-12
  • 手把手教学Win10同时安装两个版本的JDK并随时切换(JDK8和JDK11)

    手把手教学Win10同时安装两个版本的JDK并随时切换(JDK8和JDK11)

    最近在学习JDK11的一些新特性,但是日常使用基本上都是基于JDK8,因此,需要在win环境下安装多个版本的JDK,下面这篇文章主要给大家介绍了手把手教学Win10同时安装两个版本的JDK(JDK8和JDK11)并随时切换的相关资料,需要的朋友可以参考下
    2023-03-03

最新评论