MongoDB06 - MongoDB 地理空间
一:地理空间数据基础
1:地理数据表示方式
MongoDB 支持两种主要的地理空间数据表示方式: GeoJSON 格式 & 传统坐标对[经度,纬度]
- 经度在前:GeoJSON 规定坐标顺序为 [经度, 纬度]
- 有效范围:经度:-180 到 180 & 纬度:-90 到 90
1.1:GeoJSON 格式
GeoJSON支持如下的类型:
Point - 点
{
type: "Point",
coordinates: [longitude, latitude]
}
LineString - 线
{
type: "LineString",
coordinates: [[lon1,lat1], [lon2,lat2], ...]
}
Polygon - 多边形(闭合环)
{ type: "Polygon", coordinates: [
[[lon1,lat1], [lon2,lat2], ..., [lon1,lat1]] // 外环
// 可以有多个内环(洞)
]}
还有一些不太常用的:MultiPoint、MultiLineString、MultiPolygon、GeometryCollection
1.2:传统坐标对
简单数组格式:[longitude, latitude]
-> 仅适用于 2d 索引,不支持复杂几何形状
{
loc: [longitude, latitude]
}
2:地理空间索引
2.1:2dsphere 索引
地球球面几何计算,支持所有的 GeoJSON 类型,计算球面距离
支持 $nearSphere、$geoWithin、$geoIntersects
等操作
db.collection.createIndex(
{ <locationField>: "2dsphere" } // 创建索引
)
2.2:2d索引
平面几何计算,仅支持点数据(坐标对),计算平面距离(不考虑地球曲率)。·性能更高但精度较低
db.collection.createIndex(
{ <locationField>: "2d" }
)
2.3:混合索引
可以组合地理空间索引与其他字段:
db.places.createIndex(
{ location: "2dsphere", name: 1 }
)
二:地理空间查询和聚合
1:完全包含于几何图形
$geoWithin -> 查找完全包含在指定几何图形内的文档
- 使用 GeoJSON:
$geometry
- 使用传统坐标:
$box
、$polygon
、$center
、$centerSphere
db.places.find({
location: {
$geoWithin: {
// 返回在如下多边形中的文档
$geometry: {
type: "Polygon",
coordinates: [[ [0,0], [3,6], [6,1], [0,0] ]
}
}
}
})
2:与指定几何图形相交
$geoIntersects -> 查找与指定几何图形相交的文档
db.places.find({
location: {
$geoIntersects: {
// 返回和这条线相交的文档
$geometry: {
type: "LineString",
coordinates: [[0,0], [5,5]]
}
}
}
})
3:找附近点并按距离排序
$near -> 找附近点并按距离排序
db.places.find({
location: {
$near: {
// 找到举例给定中心点附近的点,最小距离100m,最大距离500m
$geometry: {
type: "Point",
coordinates: [-73.9667, 40.78]
},
$maxDistance: 500, // 米(2dsphere)
$minDistance: 100
}
}
})
4:地理空间的聚合操作
使用$geoNear
是基于距离的管道聚合,对于$geoNear
有如下的说明:
- 必须是管道的第一阶段
- 自动按距离排序
- 可返回计算的距离值
db.places.aggregate([
{
$geoNear: {
// near:参考点
// distanceField:存储距离的字段
// maxDistance/minDistance:距离范围
// spherical:是否使用球面计算
// query:附加查询条件
// $geoWithin 可在聚合中使用
// 结合 $project 计算自定义地理空间数据
near: { type: "Point", coordinates: [-73.9667, 40.78] },
distanceField: "distance",
maxDistance: 2000,
spherical: true,
query: { category: "Park" }
}
}
])
5:地理空间计算函数
$geoDistance
:计算两点间距离
{
$project: {
distance: {
$geoDistance: {
// 起点是文档中的location字段
start: "$location",
// 终点是GeoJson的坐标点
end: { type: "Point", coordinates: [-73.98, 40.77] },
distanceMultiplier: 0.001 // 转换为公里
}
}
}
}
三:实际应用示例
1:附近地点搜索
db.places.find({
location: {
$nearSphere: {
// 在给定点1km之内的文档
$geometry: {
type: "Point",
coordinates: [currentLng, currentLat]
},
$maxDistance: 1000 // 1公里内
}
},
category: "restaurant"
}).limit(20)
2:地理围栏检查
// 检查点是否在配送区域内
db.deliveryZones.find({
area: {
$geoIntersects: {
$geometry: {
type: "Point",
coordinates: [orderLng, orderLat]
}
}
}
})
3:多点距离计算
db.stores.aggregate([
{
$geoNear: {
// 指定参考点
near: { type: "Point", coordinates: [userLng, userLat] },
// 指定输出字段名,用于存储计算的距离值
distanceField: "distance",
// 使用球面
spherical: true
}
},
// 结果过滤,只要5km内的
{ $match: { distance: { $lte: 5000 } } }, // 5公里内
// 按照距离正序排序
{ $sort: { distance: 1 } }
])
四:Spring Boot Data整合
1:依赖和配置
<dependencies>
<!-- Spring Boot Starter Data MongoDB -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- 其他必要依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok (可选) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
# application.properties
spring:
data:
mongodb:
host: localhost
port: 27017
database: geo_db
2:数据模型定义
创建地理空间实体
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
@Data
@Document(collection = "places")
public class Place {
@Id
private String id;
private String name;
private String category;
private GeoJsonPoint location; // 使用GeoJsonPoint存储地理坐标
// 构造方法、getter/setter等
// Lombok的@Data注解会自动生成这些
}
地理空间索引配置
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
@Configuration
public class MongoConfig {
// 添加监听器
@Bean
public BeforeConvertListener beforeConvertListener() {
return new BeforeConvertListener();
}
// 定义监听器,继承地理事件监听器
public static class BeforeConvertListener extends AbstractMongoEventListener<Place> {
@Override
public void onBeforeConvert(BeforeConvertEvent<Place> event) {
// 说明要进行索引操作了
IndexOperations indexOps = event.getCollection().getIndexOperations();
// 定义索引
// 确保location字段有2dsphere索引
IndexDefinition indexDef =
new GeoSpatialIndexDefinition("location").typed(GeoSpatialIndexType.GEO_2DSPHERE);
// 添加索引
indexOps.ensureIndex(indexDef);
}
}
}
3:Repository 层
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.repository.Query;
import java.util.List;
public interface PlaceRepository extends MongoRepository<Place, String> {
// 查找附近的点(按距离排序)
List<Place> findByLocationNear(Point point, Distance distance);
// 查找指定类别附近的点
List<Place> findByCategoryAndLocationNear(String category, Point point, Distance distance);
// 使用GeoJSON多边形查询
@Query("{ 'location' : { $geoWithin : { $geometry : { type : 'Polygon', coordinates : ?0 } } }")
List<Place> findWithinPolygon(List<List<Double[]>> polygonCoordinates);
// 查找与指定线相交的地点
@Query("{ 'location' : { $geoIntersects : { $geometry : { type : 'LineString', coordinates : ?0 } } }")
List<Place> findIntersectingLine(List<Double[]> lineCoordinates);
// 如果是聚合查询
@Aggregation(pipeline = {
"{ $geoNear: { " +
" near: { type: 'Point', coordinates: [ ?0, ?1 ] }, " +
" distanceField: 'distance', " +
" maxDistance: ?2, " +
" spherical: true " +
"} }",
"{ $match: { category: ?3 } }",
"{ $sort: { distance: 1 } }",
"{ $limit: ?4 }"
})
List<Place> findNearbyPlacesWithAggregation(
double longitude,
double latitude,
double maxDistanceInMeters,
String category,
int limit);
}
4:服务Service层
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class GeoService {
private final PlaceRepository placeRepository;
public GeoService(PlaceRepository placeRepository) {
this.placeRepository = placeRepository;
}
/**
* 查找附近的地点
* @param longitude 经度
* @param latitude 纬度
* @param distance 距离(公里)
* @return 附近的地点列表
*/
public List<Place> findNearbyPlaces(double longitude, double latitude, double distance) {
Point point = new Point(longitude, latitude);
Distance radius = new Distance(distance, Metrics.KILOMETERS);
return placeRepository.findByLocationNear(point, radius);
}
/**
* 查找特定类别附近的地点
*/
public List<Place> findNearbyPlacesByCategory(
double longitude, double latitude, double distance, String category) {
Point point = new Point(longitude, latitude);
Distance radius = new Distance(distance, Metrics.KILOMETERS);
return placeRepository.findByCategoryAndLocationNear(category, point, radius);
}
/**
* 多边形区域查询
*/
public List<Place> findWithinPolygon(List<List<Double[]>> polygonCoordinates) {
return placeRepository.findWithinPolygon(polygonCoordinates);
}
/**
* 与线相交的地点查询
*/
public List<Place> findIntersectingLine(List<Double[]> lineCoordinates) {
return placeRepository.findIntersectingLine(lineCoordinates);
}
// 使用 GeoJson 对象
public List<Place> findWithinGeoJsonPolygon(GeoJsonPolygon polygon) {
return mongoTemplate.find(
Query.query(Criteria.where("location").within(polygon)),
Place.class
);
}
5:控制Controller层
import org.springframework.web.bind.annotation.*;
import org.springframework.data.geo.Point;
import java.util.List;
@RestController
@RequestMapping("/api/places")
public class PlaceController {
private final GeoService geoService;
public PlaceController(GeoService geoService) {
this.geoService = geoService;
}
@GetMapping("/nearby")
public List<Place> getNearbyPlaces(
@RequestParam double longitude,
@RequestParam double latitude,
@RequestParam(defaultValue = "5") double distance) {
return geoService.findNearbyPlaces(longitude, latitude, distance);
}
@GetMapping("/nearby/{category}")
public List<Place> getNearbyPlacesByCategory(
@PathVariable String category,
@RequestParam double longitude,
@RequestParam double latitude,
@RequestParam(defaultValue = "5") double distance) {
return geoService.findNearbyPlacesByCategory(longitude, latitude, distance, category);
}
@PostMapping("/within-polygon")
public List<Place> getPlacesWithinPolygon(@RequestBody List<List<Double[]>> polygonCoordinates) {
return geoService.findWithinPolygon(polygonCoordinates);
}
@PostMapping("/intersecting-line")
public List<Place> getPlacesIntersectingLine(@RequestBody List<Double[]> lineCoordinates) {
return geoService.findIntersectingLine(lineCoordinates);
}
}