MongoDB06 - MongoDB 地理空間
文章目錄
- MongoDB06 - MongoDB 地理空間
- 一:地理空間數據基礎
- 1:地理數據表示方式
- 1.1:GeoJSON 格式
- 1.2:傳統坐標對
- 2:地理空間索引
- 2.1:2dsphere 索引
- 2.2:2d索引
- 2.3:混合索引
- 二:地理空間查詢和聚合
- 1:完全包含于幾何圖形
- 2:與指定幾何圖形相交
- 3:找附近點并按距離排序
- 4:地理空間的聚合操作
- 5:地理空間計算函數
- 三:實際應用示例
- 1:附近地點搜索
- 2:地理圍欄檢查
- 3:多點距離計算
- 四:Spring Boot Data整合
- 1:依賴和配置
- 2:數據模型定義
- 3:Repository 層
- 4:服務Service層
- 5:控制Controller層
一:地理空間數據基礎
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: localhostport: 27017database: 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 {@Idprivate 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 {// 添加監聽器@Beanpublic BeforeConvertListener beforeConvertListener() {return new BeforeConvertListener();}// 定義監聽器,繼承地理事件監聽器public static class BeforeConvertListener extends AbstractMongoEventListener<Place> {@Overridepublic 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);}
}