在MongoDB的聚合管道中处理地理空间数据时,$geometry是一个无法绕开的操作符,它用来在$geoWithin、$near、$geoIntersects等阶段中嵌入GeoJSON格式的空间对象。很多查询失败并不是因为索引没建好,而是GeoJSON本身的书写不符合规范。MongoDB严格遵循RFC 7946定义的GeoJSON结构,任何字段名大小写、坐标层级、类型名称的偏差都会导致聚合直接报错或者无法命中2dsphere索引。

GeoJSON基础结构与MongoDB的强制约束
GeoJSON本质上是一种基于JSON的地理数据编码格式,在MongoDB里它必须表现为一个带有type和coordinates两个固定字段的文档。其中type的值必须是首字母大写的字符串,比如Point、LineString、Polygon,而不能写成point或者POINT。MongoDB在解析聚合管道中的$geometry时,会先校验type字段,如果不符合规范就会抛出“GeoJSON type not supported”之类的错误。
坐标的书写也有严格层级。以Point为例,coordinates必须是一个包含两个数字的数组,顺序为经度在前、纬度在后,即[经度, 纬度]。很多从其他地图API迁移过来的开发者会习惯写成[纬度, 经度],这会让查询圈完全偏到地球另一端。对于Polygon,坐标则是数组的数组,最外层代表多边形环,内层的每个数组是一个顶点。下面的代码展示了标准的Point写法:
// 正确的Point类型GeoJSON,用于$geometry
{
type: "Point",
coordinates: [ 116.404, 39.915 ] // 北京天安门,经度116.404,纬度39.915
}
除了基础结构,MongoDB默认要求GeoJSON使用WGS84坐标系,也就是经纬度用十进制表示,且范围经度在-180到180、纬度在-90到90之间。如果在聚合管道里传入了超出范围的值,即便type和coordinates结构都对,数据库也会拒绝执行。因此写$geometry之前,先确认你的原始坐标是WGS84而不是GCJ02或者BD09,后者需要提前转换。
聚合管道中$geometry的常见错误与正确示例
在实际使用$geoWithin结合$geometry做区域圈选时,新手最容易犯的错误是把Polygon的坐标写成扁平的一维数组,或者漏掉外环的闭合点。GeoJSON规定多边形首尾坐标必须相同,即第一个点和最后一个点要一致,否则在MongoDB里会被判定为非法环。下面是一段错误的写法,它少了闭合且层级不对:
// 错误示例:坐标未嵌套且未闭合
{
type: "Polygon",
coordinates: [ 116.0, 39.0, 116.1, 39.0, 116.1, 39.1, 116.0, 39.1 ]
}
正确的Polygon应当把每个顶点作为独立数组,再整体放进一个数组,并让首尾相等。在聚合管道里,它通常作为$geometry的值出现,例如查找某个矩形区域内的店铺:
db.shops.aggregate([
{
$match: {
location: {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: [[
[116.0, 39.0],
[116.1, 39.0],
[116.1, 39.1],
[116.0, 39.1],
[116.0, 39.0]
]]
}
}
}
}
}
])
另一个高频错误是在$near里混用旧版的$center写法与$geometry。MongoDB的$near支持两种语法,一种是传统{ $near: [lon, lat], $maxDistance: 1000 },另一种就是聚合或查询里用$geometry包成GeoJSON。如果选了后者,就必须保证type是Point,且不能额外在外部再写coordinates,否则会出现“无法同时指定中心点和几何对象”的冲突。理解这两种路径的差异,能减少大量调试时间。
坐标系与索引配合下的$geometry使用策略
要让聚合管道中的$geometry真正发挥作用,集合上必须存在2dsphere索引,而不是普通的2d索引。2d索引只支持平面几何和旧版坐标对,不认GeoJSON;只有2dsphere才能正确解析$geometry里的Polygon、LineString等类型。创建索引的语句很简单,但对字段类型有要求,该字段必须存的是GeoJSON对象而非裸数组。
// 对location字段建立2dsphere索引
db.shops.createIndex({ location: "2dsphere" })
// 插入的文档也应是标准GeoJSON
db.shops.insertOne({
name: "测试店",
location: {
type: "Point",
coordinates: [ 116.404, 39.915 ]
}
})
当数据量较大时,聚合管道里$geometry的查询效率直接受索引和数据格式影响。如果同一管道中先做了$match又做$group,应当把包含$geoWithin的$match尽量前置,这样MongoDB能先利用2dsphere索引裁剪掉绝大部分无关文档,再走后续聚合阶段。不少慢查询的根源,就是把空间过滤放到了$project或者$lookup之后。
对于跨国业务,还要注意GeoJSON默认不支持带高程的Coordinate Sequence之外的自定义CRS。虽然规范允许通过crs字段声明其他坐标系,但MongoDB的2dsphere索引目前实际上只认WGS84,写上非默认CRS反而会让$geometry在聚合中被忽略或报错。因此最稳妥的策略是:在应用层把任何地方坐标都转成WGS84十进制经纬度,再以标准GeoJSON结构传入$geometry,这样无论是$geoIntersects还是$nearSphere都能稳定工作。