以下是如何使用 spring data mongodb 进行地理位置相关查询的步骤和示例:
核心概念:
- geojson 对象: mongodb 推荐使用 geojson 格式来存储地理位置数据。spring data mongodb 提供了相应的 geojson 类型,如
geojsonpoint
,geojsonpolygon
,geojsonlinestring
等。geojsonpoint
: 表示一个点,例如[longitude, latitude]
。
- 地理空间索引 (geospatial index): 为了高效地执行地理位置查询,必须在存储位置数据的字段上创建地理空间索引。
2dsphere
: 支持球面几何计算,适用于地球表面的经纬度数据(推荐)。2d
: 支持平面几何计算,适用于二维平面上的点。
- 查询操作符: 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(); }; } }
总结与要点:
- 实体定义: 使用
geojsonpoint
(或其他geojson*
类型) 存储位置,并用@geospatialindexed
创建2dsphere
索引。 - 坐标顺序: 始终记住 geojson 使用
[longitude, latitude]
。spring data 的point
对象构造函数new point(x, y)
中x
是经度,y
是纬度。 - repository 查询: spring data repositories 为常见的地理位置查询(如
near
,within
)提供了便捷的方法名派生。 mongotemplate
: 对于更复杂或自定义的地理位置查询(如$geointersects
配合复杂 geojson 对象,或需要更精细控制$nearsphere
的$mindistance
/$maxdistance
),可以使用mongotemplate
。- 单位:
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+) 可以直接使用米。
- 性能: 地理空间索引对于查询性能至关重要。确保索引已正确创建。
以上就是使用spring data mongodb进行地理位置相关查询的步骤和示例的详细内容,更多关于spring data mongodb地理位置查询的资料请关注代码网其它相关文章!
发表评论