如何利用 Spring Data MongoDB 进行地理位置相关的查询?

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

核心概念:

  1. GeoJSON 对象 : MongoDB 推荐使用 GeoJSON 格式来存储地理位置数据。Spring Data MongoDB 提供了相应的 GeoJSON 类型,如 GeoJsonPoint, GeoJsonPolygon, GeoJsonLineString 等。
    • 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 的依赖:

xml 复制代码
<!-- 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 类型) 来存储位置信息。

java 复制代码
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 注解自定义查询。

java 复制代码
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 进行查询

java 复制代码
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 应用中):

java 复制代码
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 为常见的地理位置查询(如 Near, Within)提供了便捷的方法名派生。
  4. MongoTemplate : 对于更复杂或自定义的地理位置查询(如 $geoIntersects 配合复杂 GeoJSON 对象,或需要更精细控制 $nearSphere$minDistance/$maxDistance),可以使用 MongoTemplate
  5. 单位 :
    • org.springframework.data.geo.Distance: 允许你指定单位 (如 Metrics.KILOMETERS, Metrics.MILES)。Spring Data 会在与 MongoDB 交互时处理转换。
    • MongoDB 的 $nearSphere$centerSphere (用于 2dsphere 索引) 默认使用作为距离单位。
    • 当使用 MongoTemplate 时,需要注意 minDistance/maxDistance 的单位,较新版本的 MongoDB (4.0+) 和 Spring Data MongoDB (2.2+/3.0+) 可以直接使用米。
  6. 性能: 地理空间索引对于查询性能至关重要。确保索引已正确创建。
相关推荐
专注API从业者42 分钟前
Open Claw 京东商品监控选品实战:一键抓取、实时监控、高效选品
java·服务器·数据库
摇滚侠1 小时前
DBeaver 导入数据库 导入 SQL 文件 MySQL 备份恢复
java·数据库·mysql
keep one's resolveY1 小时前
SpringBoot实现重试机制的四种方案
java·spring boot·后端
天空属于哈夫克32 小时前
企业微信API常见的错误和解决方案
java·数据库·企业微信
摇滚侠3 小时前
VMvare 虚拟机 Oracle19c 安装步骤,远程连接 Oracle19c,百度网盘安装包
java·oracle
梁萌3 小时前
idea报错找不到XX包的解决方法
java·intellij-idea·启动报错·缺少包
Agent产品评测局3 小时前
生产排期与MES/ERP系统打通,实操方法详解 —— 2026企业级智能体自动化选型与实战指南
java·运维·人工智能·ai·chatgpt·自动化
阿丰资源3 小时前
基于Spring Boot的电影城管理系统(直接运行)
java·spring boot·后端
呱牛do it3 小时前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 8)
java
消失的旧时光-19434 小时前
Spring Boot 工程化进阶:统一返回 + 全局异常 + AOP 通用工具包
java·spring boot·后端·aop·自定义注解