基于MongoDB实现位置分组查询,筛选间距≥10公里的结果
解决方案:基于地理网格分组实现点聚合
针对1000万条充电站数据的地图展示性能问题,我们可以通过地理网格分组的方式,将距离小于10公里的充电站合并为一个返回结果,具体实现如下:
方案一:自定义经纬度网格分组(精准10公里间隔)
该方案通过计算每个充电站所在的10公里网格编号,按网格分组后每组返回一条数据,能精准控制点之间的最小间距:
const stations = require('./path/to/your/stationsModel'); // 地图视口过滤(可选,优先过滤减少数据量) const mapBounds = { sw: [10.0, 40.0], // 西南角经纬度 [经度, 纬度] ne: [20.0, 50.0] // 东北角经纬度 }; stations.aggregate([ // 第一步:过滤当前地图视口内的数据(大幅减少后续处理量) { $match: { loc: { $geoWithin: { $box: [mapBounds.sw, mapBounds.ne] } } } }, // 第二步:计算每个点所属的10公里网格编号 { $addFields: { // 纬度每度≈111公里,10公里对应≈0.09009度,取整到该倍数的网格 latGrid: { $round: [{ $divide: ["$loc.coordinates.1", 0.09009] }, 0] }, // 经度距离随纬度变化,计算10公里对应的经度间隔后取整 lngGrid: { $round: [ { $divide: [ "$loc.coordinates.0", { $divide: [ 10, { $multiply: [111, { $cos: { $multiply: ["$loc.coordinates.1", Math.PI / 180] } }] } ] } ] }, 0 ] } } }, // 第三步:按网格分组,每组随机取一条数据(也可用$first取第一条) { $group: { _id: { latGrid: "$latGrid", lngGrid: "$lngGrid" }, station: { $sample: { size: 1 } } } }, // 第四步:将分组后的station作为根文档返回 { $replaceRoot: { newRoot: "$station" } } ]) .exec() .then(aggregatedStations => { // 处理返回的聚合后数据 console.log(aggregatedStations); }) .catch(err => { console.error('聚合查询失败:', err); });
方案二:GeoHash分组(快速实现近似间隔)
GeoHash是一种将地理位置编码为字符串的算法,相同前缀的GeoHash代表同一区域,可快速实现近似的区域聚合:
stations.aggregate([ // 可选:先过滤视口数据 { $match: { loc: { $geoWithin: { $box: [mapBounds.sw, mapBounds.ne] } } } }, // 计算GeoHash值 { $addFields: { geoHash: { $geoHash: "$loc" } } }, // 取GeoHash前5位(对应约4.9公里精度,若要接近10公里可改用前4位,对应约39公里) { $group: { _id: { $substr: ["$geoHash", 0, 5] }, station: { $sample: { size: 1 } } } }, { $replaceRoot: { newRoot: "$station" } } ]) .exec() .then(results => { console.log(results); });
性能优化建议
- 优先过滤视口数据:在聚合的第一个阶段使用
$match过滤当前地图视口内的数据,避免处理全量1000万条数据。 - 确保索引生效:你的Schema已添加
2dsphere索引,$geoWithin和地理相关聚合会自动使用该索引,大幅提升查询速度。 - 选择合适的分组方式:需要精准10公里间隔用方案一,追求快速开发用方案二。
- 避免返回冗余字段:可在聚合中添加
$project阶段,只返回地图展示需要的字段(如title、loc等),减少数据传输量。
内容的提问来源于stack exchange,提问作者Nihat Yıldız - NR42
相关产品推荐
相关产品推荐

