$geowithin配合$geometry(geojson polygon)是统计任意形状区域内订单量的准确方案,需建2dsphere索引、坐标顺序为[longitude, latitude]且polygon闭合;$centersphere仅支持圆形,不适用于真实不规则区域。

直接结论:用 $geoWithin + $geometry(GeoJSON Polygon)能准确圈出任意形状区域并统计订单量,但必须确保字段已建 2dsphere 索引,且坐标顺序是 [longitude, latitude] —— 写反了会查不到数据。
为什么不用 $centerSphere 而用 $geometry
虽然 $centerSphere 写起来简单(比如查圆形半径 5km),但它只支持正圆,无法表达商圈、行政区、配送范围等真实不规则区域。而实际业务中“区域内订单量”几乎都指 Polygon 边界内,不是“离某点 X 公里内”。$geoWithin 配合 $geometry 才是真正可控的地理围栏方案。
常见错误现象:
– 查询返回空结果,但明明有数据在地图上显示在该区域内
– $geoWithin 不报错,但结果明显漏掉边缘订单
– 使用 $centerSphere 时距离单位换算错(比如把公里当英里除以 3963.191)
- 务必确认你传入的 Polygon 坐标是闭合的:首尾坐标必须完全一致,例如
[[116.4,39.9], [116.5,39.9], [116.5,39.8], [116.4,39.8], [116.4,39.9]] - 所有坐标对必须是
[经度, 纬度],MongoDB 严格按此顺序解析;写成[纬度, 经度]会导致整个 Polygon 落在南美洲或南极附近 - 如果 Polygon 面积超过半个地球(极少见),需显式指定
crs,否则 MongoDB 会查互补区域 —— 这是文档里埋得最深的坑之一
聚合管道中组合 $match 与 $geoWithin 的写法
不能把 $geoWithin 放在 $match 外层当独立阶段用 —— 它本身就是一个查询条件,必须嵌在 $match 内部。正确结构是:
db.orders.aggregate([
{
$match: {
"location": {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: [
[[116.4, 39.9], [116.5, 39.9], [116.5, 39.8], [116.4, 39.8], [116.4, 39.9]]
]
}
}
}
}
},
{ $count: "total" }
])
使用场景:
– 实时看某个商场周边步行 1km 范围内今日订单量
– 统计某城市行政区划图层内过去 24 小时新下单数
– 核对骑手配送区域与实际订单分布是否匹配
-
location字段必须是 GeoJSON Point 类型,即形如{ type: "Point", coordinates: [116.4074, 39.9042] },不能是 legacy 格式{ lon: 116.4074, lat: 39.9042 } - 若要同时加时间、状态等筛选,全部塞进同一个
$match,不要拆成多个$match阶段 —— 否则地理索引可能失效 - 聚合中
$geoWithin不排序、不计算距离,所以性能比$geoNear高很多,适合纯计数场景
索引和性能关键点
没有 2dsphere 索引时,$geoWithin 会全表扫描,10 万订单可能耗时数秒;加上索引后通常在毫秒级。但索引建错也白搭。
- 建索引命令必须明确指定字段路径和类型:
db.orders.createIndex({ "location": "2dsphere" }),不能写成{"location.coordinates": "2dsphere"} - 如果
location是嵌套字段(如address.location),索引键就得写成{"address.location": "2dsphere"} - 一个集合只能有一个
2dsphere索引作用于该字段;若已有其他地理索引(如2d),先删掉再重建,避免冲突 - 验证索引是否生效:用
db.orders.explain("executionStats").aggregate([...])查executionStages中是否有geoShard或IXSCAN并命中2dsphere
最容易被忽略的是坐标系语义 —— 即使 Polygon 画得完全正确、索引也建了,只要有一处坐标顺序颠倒或小数位精度丢失(比如前端 JS 把 116.407396 四舍五入成 116.4074),就可能导致点刚好落在 Polygon 边界外侧,而这种误差在地图可视化里根本看不出来。











