Flutter:基于Haversine/Great Circle Distance实现本地数据库地点高效筛选
嘿,这个问题我刚好有实践经验,咱们可以用haversine或者great_circle_distance结合本地数据库的边界框预筛选来高效实现,完全不用依赖Google Maps API,也不用遍历全量数据。下面分步骤给你讲清楚:
第一步:安装依赖
先在你的pubspec.yaml里添加需要的插件(选一个就行,功能差异不大):
dependencies: flutter: sdk: flutter haversine: ^1.0.2 # 或者用 great_circle_distance: ^1.0.1 sqflite: ^2.3.0 # 假设你用sqflite做本地数据库,可根据实际情况调整
然后运行flutter pub get安装依赖。
需求1:生成当前位置周边的范围数值(边界框)
为了避免全量计算所有地点的距离,我们先计算一个经纬度边界框——也就是当前位置周围半径范围内的最大/最小纬度、经度值。这个边界框就是你需要的“周边范围数值”,同时能帮我们快速从数据库过滤出候选地点,大大减少后续计算量。
用基础地理公式就能计算(不用插件也能搞定):
import 'dart:math'; /// 根据当前经纬度和半径(公里)计算边界框 Map<String, double> calculateBoundingBox(double currentLat, double currentLng, double radiusKm) { const earthAvgRadius = 6371.0; // 地球平均半径(公里) // 计算纬度偏移量:每公里对应的纬度度数 final latOffset = (radiusKm / earthAvgRadius) * (180 / pi); // 计算经度偏移量:受纬度影响,需要乘以cos(纬度) final lngOffset = (radiusKm / (earthAvgRadius * cos(currentLat * pi / 180))) * (180 / pi); return { 'minLat': currentLat - latOffset, 'maxLat': currentLat + latOffset, 'minLng': currentLng - lngOffset, 'maxLng': currentLng + lngOffset, }; }
比如你在圣保罗(-23.5505, -46.6333)、要筛选500公里半径的地点,调用这个函数就能得到对应的经纬度边界范围。
需求2:本地数据库筛选附近地点(高效版)
核心思路是两步筛选:先通过数据库的WHERE条件过滤边界框内的候选地点,再用距离插件计算精确距离,最终筛选出符合半径要求的地点。
步骤1:数据库预筛选(关键优化)
先给数据库表的latitude和longitude字段添加索引(这步很重要,能让边界框查询速度提升数倍)。然后用SQL语句快速过滤候选地点:
import 'package:sqflite/sqflite.dart'; import 'package:haversine/haversine.dart'; // 假设你的数据库表叫`places`,包含id、name、latitude、longitude字段 Future<List<Map<String, dynamic>>> fetchNearbyPlaces(double currentLat, double currentLng, double radiusKm) async { // 1. 获取数据库实例(替换成你的数据库初始化逻辑) final Database db = await openDatabase( 'your_database.db', version: 1, ); // 2. 计算边界框 final boundingBox = calculateBoundingBox(currentLat, currentLng, radiusKm); // 3. 查询边界框内的候选地点(利用索引快速过滤) final candidatePlaces = await db.query( 'places', where: 'latitude BETWEEN ? AND ? AND longitude BETWEEN ? AND ?', whereArgs: [ boundingBox['minLat'], boundingBox['maxLat'], boundingBox['minLng'], boundingBox['maxLng'], ], ); // 4. 用haversine计算精确距离,筛选最终符合条件的地点 final List<Map<String, dynamic>> nearbyPlaces = []; final haversine = Haversine(); for (final place in candidatePlaces) { final placeLat = place['latitude'] as double; final placeLng = place['longitude'] as double; // 计算当前位置到该地点的距离(公里) final distance = haversine.distance( LatLng(currentLat, currentLng), LatLng(placeLat, placeLng), Unit.Kilometer, ); // 如果距离在半径范围内,加入结果列表(可顺便带上距离字段) if (distance <= radiusKm) { nearbyPlaces.add({...place, 'distance_km': distance.toStringAsFixed(2)}); } } return nearbyPlaces; }
如果用great_circle_distance,替换距离计算部分即可:
import 'package:great_circle_distance/great_circle_distance.dart'; // 替换上面的距离计算代码 final distance = GreatCircleDistance( latitude1: currentLat, longitude1: currentLng, latitude2: placeLat, longitude2: placeLng, ).distance(Unit.Kilometers);
关键注意事项
- 一定要给经纬度字段加索引:没有索引的话,数据库会全表扫描,效率大打折扣。
- 边界框是“近似范围”:因为地球是球体,边界框筛选出的候选地点可能有少量超出半径,但后续的精确距离计算会把它们排除,这个误差完全可以接受。
- 性能优势:如果数据库有上万条数据,这种两步筛选的方式比全量计算距离快10倍以上,非常高效。
内容的提问来源于stack exchange,提问作者Colque Paxi
相关产品推荐
相关产品推荐

